Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,11 @@ YASGUI is built as a monorepo with four main packages, each serving a specific p
- DOM manipulation utilities
- Local storage abstraction
- Common helper functions
- SVG icon rendering
- DOMPurify integration for XSS protection

**Exports:**
- `Storage`: localStorage abstraction with namespacing
- `addClass`, `removeClass`, `hasClass`: DOM class utilities
- `drawSvgStringAsElement`: SVG helper
- `drawFontAwesomeIconAsSvg`: Icon rendering

#### @matdata/yasqe (SPARQL Query Editor)

Expand Down
28 changes: 18 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
]
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.1.0",
"@matdata/yasgui-graph-plugin": "^1.4.1",
"@matdata/yasgui-table-plugin": "^1.2.0",
"@typescript-eslint/eslint-plugin": "^6.13.2",
Expand Down
30 changes: 0 additions & 30 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,6 @@ export { default as Storage } from "./Storage";

const { sanitize } = DOMPurify;

export function drawSvgStringAsElement(svgString: string) {
if (svgString && svgString.trim().indexOf("<svg") == 0) {
//no style passed via config. guess own styles
var parser = new DOMParser();
var dom = parser.parseFromString(svgString, "text/xml");
var svg = dom.documentElement;
svg.setAttribute("aria-hidden", "true");

var svgContainer = document.createElement("div");
svgContainer.className = "svgImg";
svgContainer.appendChild(svg);
return svgContainer;
}
throw new Error("No svg string given. Cannot draw");
}
export interface FaIcon {
width: number;
height: number;
svgPathData: string;
}

/**
* Draws font fontawesome icon as svg. This is a lot more lightweight then the option that is offered by fontawesome
* @param faIcon
* @returns
*/
export function drawFontAwesomeIconAsSvg(faIcon: FaIcon) {
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${faIcon.width} ${faIcon.height}" aria-hidden="true"><path fill="currentColor" d="${faIcon.svgPathData}"></path></svg>`;
}

export function hasClass(el: Element | undefined, className: string) {
if (!el) return;
if (el.classList) return el.classList.contains(className);
Expand Down
45 changes: 20 additions & 25 deletions packages/yasgui/src/Tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,6 @@ import { getWorkspaceBackend } from "./queryManagement/backends/getWorkspaceBack
import { asWorkspaceBackendError } from "./queryManagement/backends/errors";
import { normalizeQueryFilename } from "./queryManagement/normalizeQueryFilename";

// Layout orientation toggle icons
const HORIZONTAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24">
<rect x="2" y="4" width="9" height="16" stroke="currentColor" stroke-width="2" fill="none"/>
<rect x="13" y="4" width="9" height="16" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>`;

const VERTICAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24">
<rect x="2" y="2" width="20" height="8" stroke="currentColor" stroke-width="2" fill="none"/>
<rect x="2" y="12" width="20" height="10" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>`;

// Overflow dropdown icon (three horizontal dots / ellipsis menu)
const OVERFLOW_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
<circle cx="5" cy="12" r="2"/>
<circle cx="12" cy="12" r="2"/>
<circle cx="19" cy="12" r="2"/>
</svg>`;

export interface PersistedJsonYasr extends YasrPersistentConfig {
responseSummary: Parser.ResponseSummary;
}
Expand Down Expand Up @@ -569,8 +551,11 @@ export class Tab extends EventEmitter {
if (!this.orientationToggleButton) return;

// Show the icon for the layout we'll switch TO (not the current layout)
// fa-columns for horizontal (side-by-side), fa-grip-lines for vertical (stacked)
this.orientationToggleButton.innerHTML =
this.currentOrientation === "vertical" ? HORIZONTAL_LAYOUT_ICON : VERTICAL_LAYOUT_ICON;
this.currentOrientation === "vertical"
? '<i class="fas fa-grip-lines-vertical"></i>'
: '<i class="fas fa-grip-lines"></i>';
this.orientationToggleButton.title =
this.currentOrientation === "vertical" ? "Switch to horizontal layout" : "Switch to vertical layout";
}
Expand Down Expand Up @@ -746,7 +731,7 @@ export class Tab extends EventEmitter {
if (!this.endpointOverflowButton) {
this.endpointOverflowButton = document.createElement("button");
addClass(this.endpointOverflowButton, "endpointOverflowBtn");
this.endpointOverflowButton.innerHTML = OVERFLOW_ICON;
this.endpointOverflowButton.innerHTML = '<i class="fas fa-ellipsis-vertical"></i>';
this.endpointOverflowButton.title = "More endpoints";
this.endpointOverflowButton.setAttribute("aria-label", "More endpoint options");
this.endpointOverflowButton.setAttribute("aria-haspopup", "true");
Expand Down Expand Up @@ -1310,7 +1295,13 @@ export class Tab extends EventEmitter {
}
this.yasqe = new Yasqe(this.yasqeWrapperEl, yasqeConf);

this.initSaveManagedQueryIcon();
// Hook up the save button to managed query save
this.yasqe.on("saveManagedQuery", () => {
void this.saveManagedQueryOrSaveAsManagedQuery();
});

// Show/hide save button based on workspace configuration
this.updateSaveButtonVisibility();

this.yasqe.on("blur", this.handleYasqeBlur);
this.yasqe.on("query", this.handleYasqeQuery);
Expand All @@ -1327,6 +1318,13 @@ export class Tab extends EventEmitter {
this.attachYasqeMouseHandler();
}

private updateSaveButtonVisibility() {
if (!this.yasqe) return;
const workspaces = this.yasgui.persistentConfig.getWorkspaces();
const hasWorkspaces = workspaces && workspaces.length > 0;
this.yasqe.setSaveButtonVisible(hasWorkspaces);
}

private initSaveManagedQueryIcon() {
if (!this.yasqe) return;

Expand All @@ -1345,10 +1343,7 @@ export class Tab extends EventEmitter {
saveBtn.className = "yasqe_saveManagedQueryButton";
saveBtn.title = "Save managed query";
saveBtn.setAttribute("aria-label", "Save managed query");
saveBtn.innerHTML =
'<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
'<path d="M17 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7l-4-4zm-1 2l3 3h-3V5zM7 5h7v4H7V5zm5 16H7v-6h5v6zm2 0v-6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v6H5V5h1v4a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5h1v5h4v11h-6z"/>' +
"</svg>";
saveBtn.innerHTML = '<i class="fas fa-save" aria-hidden="true"></i>';

saveBtn.addEventListener("click", (e) => {
e.preventDefault();
Expand Down
13 changes: 2 additions & 11 deletions packages/yasgui/src/TabElements.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ $minTabHeight: 35px;
&:hover,
&:focus-visible {
color: var(--yasgui-button-hover, #000);
fill: var(--yasgui-button-hover, #000);
background: var(--yasgui-bg-secondary, #f7f7f7);
}

svg {
width: 20px;
height: 20px;
i {
font-size: 20px;
}
}

Expand Down Expand Up @@ -60,22 +58,15 @@ $minTabHeight: 35px;
background: transparent;
border: none;
color: var(--yasgui-button-text, #505050);
fill: var(--yasgui-button-text, #505050);
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;

&:hover {
color: var(--yasgui-button-hover, #000);
fill: var(--yasgui-button-hover, #000);
background: var(--yasgui-bg-secondary, #f7f7f7);
}

svg {
width: 20px;
height: 20px;
}
}

.addTab {
Expand Down
10 changes: 1 addition & 9 deletions packages/yasgui/src/TabElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import { hasClass, addClass, removeClass } from "@matdata/yasgui-utils";
import sortablejs from "sortablejs";
import "./TabElements.scss";

// Theme toggle icons
const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>`;

const SUN_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>`;
export interface TabList {}
export class TabListEl {
private tabList: TabList;
Expand Down Expand Up @@ -323,7 +315,7 @@ export class TabList {
queryBrowserButton.className = "queryBrowserToggle";
queryBrowserButton.setAttribute("aria-label", "Open query browser");
queryBrowserButton.title = "Open query browser";
queryBrowserButton.innerHTML = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z"/></svg>`;
queryBrowserButton.innerHTML = '<i class="fas fa-bars"></i>';
queryBrowserButton.addEventListener("click", () => {
this.yasgui.queryBrowser.toggle(queryBrowserButton);
});
Expand Down
5 changes: 0 additions & 5 deletions packages/yasgui/src/TabSettingsModal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@
align-items: center;
justify-content: center;

svg {
width: 20px;
height: 20px;
}

&:hover {
opacity: 0.7;
}
Expand Down
25 changes: 5 additions & 20 deletions packages/yasgui/src/TabSettingsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@ import * as OAuth2Utils from "./OAuth2Utils";
import PersistentConfig from "./PersistentConfig";
import { WorkspaceSettingsForm } from "./queryManagement/WorkspaceSettingsForm";

// Theme toggle icons
const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>`;

const SUN_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>`;

const SETTINGS_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-7.43 2.52c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/>
</svg>`;

const AcceptOptionsMap: { key: string; value: string }[] = [
{ key: "JSON", value: "application/sparql-results+json" },
{ key: "XML", value: "application/sparql-results+xml" },
Expand Down Expand Up @@ -67,7 +54,7 @@ export default class TabSettingsModal {
addClass(this.settingsButton, "tabContextButton");
this.settingsButton.setAttribute("aria-label", "Settings");
this.settingsButton.title = "Settings";
this.settingsButton.innerHTML = SETTINGS_ICON;
this.settingsButton.innerHTML = '<i class="fas fa-cog"></i>';
this.settingsButton.onclick = () => this.open();
controlBarEl.appendChild(this.settingsButton);

Expand All @@ -89,9 +76,7 @@ export default class TabSettingsModal {
this.prefixButton = document.createElement("button");
this.prefixButton.setAttribute("aria-label", "Insert Prefixes");
this.prefixButton.title = "Insert saved prefixes into query";
this.prefixButton.innerHTML = `<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/>
</svg>`;
this.prefixButton.innerHTML = '<i class="fas fa-arrow-up-right-from-square"></i>';
addClass(this.prefixButton, "tabContextButton", "prefixButton");
controlBarEl.appendChild(this.prefixButton);
this.prefixButton.onclick = () => this.insertPrefixesIntoQuery();
Expand Down Expand Up @@ -1526,9 +1511,9 @@ export default class TabSettingsModal {

private getThemeToggleIcon(): string {
const currentTheme = this.tab.yasgui.getTheme();
// In dark mode, show moon icon (clicking will switch to light)
// In light mode, show sun icon (clicking will switch to dark)
return currentTheme === "dark" ? MOON_ICON : SUN_ICON;
// In dark mode, show lightbulb icon (clicking will switch to light)
// In light mode, show moon icon (clicking will switch to dark)
return currentTheme === "dark" ? '<i class="fas fa-lightbulb"></i>' : '<i class="fas fa-moon"></i>';
}

private drawImportExportSettings(container: HTMLElement) {
Expand Down
5 changes: 2 additions & 3 deletions packages/yasgui/src/endpointSelect.scss
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,8 @@
flex-shrink: 0;
transition: all 0.2s ease;

svg {
width: 16px;
height: 16px;
i {
font-size: 16px;
}

&:hover {
Expand Down
4 changes: 4 additions & 0 deletions packages/yasgui/src/index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Font Awesome icons
@import "@fortawesome/fontawesome-free/css/fontawesome.css";
@import "@fortawesome/fontawesome-free/css/solid.css";

// Main YASGUI container - fills 100% of parent element
// Parent element (typically body) should have height: 100%; width: 100%
.yasgui {
Expand Down
12 changes: 4 additions & 8 deletions packages/yasgui/src/queryManagement/QueryBrowser.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,8 @@
align-items: center;
justify-content: center;

svg {
width: 20px;
height: 20px;
i {
font-size: 20px;
}

&:hover {
Expand Down Expand Up @@ -347,14 +346,11 @@
}

&__empty-icon {
width: 64px;
height: 64px;
color: var(--yasgui-text-secondary, #999);
opacity: 0.6;

svg {
width: 100%;
height: 100%;
i {
font-size: 64px;
}
}

Expand Down
Loading