Skip to content
Closed
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
32 changes: 32 additions & 0 deletions docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,38 @@ const plugins = yasr.getPlugins();
console.log('Available plugins:', Object.keys(plugins));
```

##### `getAvailablePlugins(): { name: string; label: string; priority: number }[]`

Get list of all available plugins with their labels and priorities (excludes hidden plugins).

```javascript
const availablePlugins = yasr.getAvailablePlugins();
console.log('Available plugins:', availablePlugins);
// Output: [{ name: 'table', label: 'Table', priority: 10 }, ...]
```

##### `getPluginOrder(): { select?: string[]; construct?: string[] } | undefined`

Get the current plugin order preferences for SELECT/ASK and CONSTRUCT/DESCRIBE queries.

```javascript
const pluginOrder = yasr.getPluginOrder();
console.log('SELECT order:', pluginOrder?.select);
console.log('CONSTRUCT order:', pluginOrder?.construct);
```

##### `setPluginOrder(order: { select?: string[]; construct?: string[] }): void`

Set plugin order preferences to customize which plugins are preferred for different query types.

```javascript
// Set preferred order for SELECT queries
yasr.setPluginOrder({
select: ['response', 'table'],
construct: ['graph', 'response']
});
```

##### `download(filename?: string): void`

Download results using current plugin's download method.
Expand Down
25 changes: 25 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1142,12 +1142,37 @@ YASGUI automatically selects the most appropriate plugin based on:
- Query type (SELECT, CONSTRUCT, ASK, DESCRIBE)
- Response content type
- Data structure
- Your configured plugin order preferences (see below)

**Manual Selection:**
- Use the plugin selector buttons at the top of the results area
- Available plugins depend on the query results
- Your selection is saved per tab

**Configuring Plugin Order Preferences:**

You can customize the order in which plugins are preferred when displaying query results. This allows you to prioritize your favorite visualization methods.

1. **Open Settings**: Click the gear icon (⚙️) in the control bar
2. **Navigate to Output Preferences**: Select the "Output Preferences" tab in the settings sidebar
3. **Configure Preferences**: You'll see two separate lists:
- **SELECT / ASK Query Results**: For tabular query results
- **CONSTRUCT / DESCRIBE Query Results**: For graph query results
4. **Reorder Plugins**: Drag plugins using the handle (☰) to reorder them
- Plugins at the top of the list will be preferred when displaying results
- The first compatible plugin in your list will be automatically selected
5. **Save Changes**: Your preferences are saved automatically as you reorder

**Example Use Cases:**
- If you prefer viewing raw JSON responses, move the "Response" plugin to the top of the SELECT list
- For graph visualizations, prioritize the "Graph" plugin over the "Response" plugin in the CONSTRUCT list
- Customize separately for different query types based on your workflow

**Notes:**
- Plugin order preferences are persistent and apply across all tabs
- The actual plugin used depends on both your preference order and plugin compatibility with the current results
- If your preferred plugin cannot handle the current results, the next compatible plugin in your list will be used automatically

---

## Keyboard Shortcuts
Expand Down
77 changes: 77 additions & 0 deletions packages/yasgui/src/TabSettingsModal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1085,3 +1085,80 @@
transform: translateY(1px);
}
}

// Plugin Order Settings
.pluginOrderSection {
margin-bottom: 30px;
}

.pluginOrderList {
list-style: none;
padding: 0;
margin: 10px 0;
border: 1px solid var(--yasgui-border-color, #ddd);
border-radius: 4px;
background: var(--yasgui-bg-secondary, #f9f9f9);
max-width: 500px;
}

.pluginOrderItem {
display: flex;
align-items: center;
padding: 12px 16px;
background: var(--yasgui-bg-primary, white);
border-bottom: 1px solid var(--yasgui-border-color, #ddd);
cursor: move;
user-select: none;
transition:
background-color 0.2s,
box-shadow 0.2s;

&:last-child {
border-bottom: none;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}

&:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}

&:hover {
background: var(--yasgui-bg-hover, #f5f5f5);
}
}

.pluginOrderDragHandle {
margin-right: 12px;
font-size: 18px;
color: var(--yasgui-text-muted, #666);
cursor: grab;
user-select: none;

&:active {
cursor: grabbing;
}
}

.pluginOrderLabel {
flex: 1;
font-size: 14px;
font-weight: 500;
color: var(--yasgui-text-primary, #333);
}

.pluginOrderGhost {
opacity: 0.4;
background: var(--yasgui-bg-hover, #f5f5f5);
}

.pluginOrderChosen {
background: var(--yasgui-accent-light, #e6f2ff);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

.pluginOrderDrag {
opacity: 0.8;
transform: rotate(2deg);
}
180 changes: 180 additions & 0 deletions packages/yasgui/src/TabSettingsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ConfigExportImport from "./ConfigExportImport";
import { VERSION } from "./version";
import * as OAuth2Utils from "./OAuth2Utils";
import PersistentConfig from "./PersistentConfig";
import sortablejs from "sortablejs";

// Theme toggle icons
const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
Expand Down Expand Up @@ -142,6 +143,11 @@ export default class TabSettingsModal {
addClass(prefixTab, "modalNavButton");
prefixTab.onclick = () => this.switchTab("prefix");

const outputTab = document.createElement("button");
outputTab.textContent = "Output Preferences";
addClass(outputTab, "modalNavButton");
outputTab.onclick = () => this.switchTab("output");

const editorTab = document.createElement("button");
editorTab.textContent = "Editor";
addClass(editorTab, "modalNavButton");
Expand All @@ -165,6 +171,7 @@ export default class TabSettingsModal {
sidebar.appendChild(requestTab);
sidebar.appendChild(endpointsTab);
sidebar.appendChild(prefixTab);
sidebar.appendChild(outputTab);
sidebar.appendChild(editorTab);
sidebar.appendChild(importExportTab);
sidebar.appendChild(shortcutsTab);
Expand Down Expand Up @@ -193,6 +200,11 @@ export default class TabSettingsModal {
prefixContent.id = "prefix-content";
this.drawPrefixSettings(prefixContent);

const outputContent = document.createElement("div");
addClass(outputContent, "modalTabContent");
outputContent.id = "output-content";
this.drawOutputSettings(outputContent);

const editorContent = document.createElement("div");
addClass(editorContent, "modalTabContent");
editorContent.id = "editor-content";
Expand All @@ -216,6 +228,7 @@ export default class TabSettingsModal {
contentArea.appendChild(requestContent);
contentArea.appendChild(endpointsContent);
contentArea.appendChild(prefixContent);
contentArea.appendChild(outputContent);
contentArea.appendChild(editorContent);
contentArea.appendChild(importExportContent);
contentArea.appendChild(shortcutsContent);
Expand Down Expand Up @@ -327,6 +340,173 @@ export default class TabSettingsModal {
container.appendChild(section);
}

private drawOutputSettings(container: HTMLElement) {
const yasr = this.tab.getYasr();
if (!yasr) {
const notice = document.createElement("p");
addClass(notice, "settingsHelp");
notice.textContent = "Output settings will be available after running a query.";
container.appendChild(notice);
return;
}

// Introduction
const intro = document.createElement("p");
addClass(intro, "settingsHelp");
intro.textContent =
"Configure the preferred order of output plugins for different query types. Plugins at the top of the list will be preferred when displaying results.";
container.appendChild(intro);

// Get available plugins
const availablePlugins = yasr.getAvailablePlugins();
const currentOrder = yasr.getPluginOrder() || { select: [], construct: [] };

// Initialize with priority-based order if empty
if (!currentOrder.select || currentOrder.select.length === 0) {
currentOrder.select = availablePlugins.map((p) => p.name);
}
if (!currentOrder.construct || currentOrder.construct.length === 0) {
currentOrder.construct = availablePlugins.map((p) => p.name);
}

// SELECT/ASK queries section
const selectSection = document.createElement("div");
addClass(selectSection, "settingsSection", "pluginOrderSection");

const selectLabel = document.createElement("h3");
addClass(selectLabel, "settingsLabel");
selectLabel.textContent = "SELECT / ASK Query Results";

const selectHelp = document.createElement("p");
addClass(selectHelp, "settingsHelp");
selectHelp.textContent = "Preferred plugin order for tabular query results (SELECT, ASK).";

const selectList = document.createElement("ul");
addClass(selectList, "pluginOrderList");
selectList.id = "select-plugin-order";

// Populate SELECT list
currentOrder.select.forEach((pluginName) => {
const plugin = availablePlugins.find((p) => p.name === pluginName);
if (plugin) {
const item = this.createPluginOrderItem(plugin.name, plugin.label);
selectList.appendChild(item);
}
});

// Add any plugins not in the current order
availablePlugins.forEach((plugin) => {
if (!currentOrder.select!.includes(plugin.name)) {
const item = this.createPluginOrderItem(plugin.name, plugin.label);
selectList.appendChild(item);
}
});

selectSection.appendChild(selectLabel);
selectSection.appendChild(selectHelp);
selectSection.appendChild(selectList);

// CONSTRUCT/DESCRIBE queries section
const constructSection = document.createElement("div");
addClass(constructSection, "settingsSection", "pluginOrderSection");

const constructLabel = document.createElement("h3");
addClass(constructLabel, "settingsLabel");
constructLabel.textContent = "CONSTRUCT / DESCRIBE Query Results";

const constructHelp = document.createElement("p");
addClass(constructHelp, "settingsHelp");
constructHelp.textContent = "Preferred plugin order for graph query results (CONSTRUCT, DESCRIBE).";

const constructList = document.createElement("ul");
addClass(constructList, "pluginOrderList");
constructList.id = "construct-plugin-order";

// Populate CONSTRUCT list
currentOrder.construct.forEach((pluginName) => {
const plugin = availablePlugins.find((p) => p.name === pluginName);
if (plugin) {
const item = this.createPluginOrderItem(plugin.name, plugin.label);
constructList.appendChild(item);
}
});

// Add any plugins not in the current order
availablePlugins.forEach((plugin) => {
if (!currentOrder.construct!.includes(plugin.name)) {
const item = this.createPluginOrderItem(plugin.name, plugin.label);
constructList.appendChild(item);
}
});

constructSection.appendChild(constructLabel);
constructSection.appendChild(constructHelp);
constructSection.appendChild(constructList);

container.appendChild(selectSection);
container.appendChild(constructSection);

// Initialize drag and drop
this.initializePluginOrderDragDrop(selectList, "select", yasr);
this.initializePluginOrderDragDrop(constructList, "construct", yasr);
}

private createPluginOrderItem(pluginName: string, pluginLabel: string): HTMLLIElement {
const item = document.createElement("li");
addClass(item, "pluginOrderItem");
item.setAttribute("data-plugin-name", pluginName);

const dragHandle = document.createElement("span");
addClass(dragHandle, "pluginOrderDragHandle");
dragHandle.innerHTML = "☰"; // Hamburger menu icon
dragHandle.title = "Drag to reorder";

const label = document.createElement("span");
addClass(label, "pluginOrderLabel");
label.textContent = pluginLabel;

item.appendChild(dragHandle);
item.appendChild(label);

return item;
}

private initializePluginOrderDragDrop(listEl: HTMLElement, queryType: "select" | "construct", yasr: any) {
sortablejs.create(listEl, {
animation: 150,
handle: ".pluginOrderDragHandle",
ghostClass: "pluginOrderGhost",
chosenClass: "pluginOrderChosen",
dragClass: "pluginOrderDrag",
onEnd: () => {
// Get the new order from the list
const items = listEl.querySelectorAll("li[data-plugin-name]");
const newOrder: string[] = [];
items.forEach((item) => {
const pluginName = item.getAttribute("data-plugin-name");
if (pluginName) {
newOrder.push(pluginName);
}
});

// Save the new order
const currentOrder = yasr.getPluginOrder() || { select: [], construct: [] };
if (queryType === "select") {
currentOrder.select = newOrder;
} else {
currentOrder.construct = newOrder;
}
yasr.setPluginOrder(currentOrder);

// Show feedback
this.showNotification(
`${queryType === "select" ? "SELECT/ASK" : "CONSTRUCT/DESCRIBE"} plugin order updated`,
"success",
);
},
});
}

private drawEditorSettings(container: HTMLElement) {
const yasqe = this.tab.getYasqe();
if (!yasqe) {
Expand Down
Loading