feat(datasette): proof of concept - create JS plugin for adding column names to clipboard

pull/2052/head
Cameron Yick 2023-04-02 13:23:07 -04:00
rodzic ab9bc5fd19
commit b30e6096e7
2 zmienionych plików z 103 dodań i 17 usunięć

Wyświetl plik

@ -29,6 +29,28 @@ const datasetteManager = {
console.warn(`Warning -> plugin ${name} is redefined`);
}
datasetteManager.plugins.set(name, pluginMetadata);
// Check which commands to include!
},
/** Loop across plugins... note no control over item order
* Items must have a label (text), may have an href or an onclick.
*/
getColumnHeaderItems: (columnName) => {
let items = [];
datasetteManager.plugins.forEach(plugin => {
// Accept function that returns list of items with keys
// Must have: text label (for now, no custom DOM node renderprop)
// One of these: onClick or href
if (plugin.getColumnHeaderItems) {
items.push(...plugin.getColumnHeaderItems(columnName));
}
});
// TODO: validate item configs
return items;
},
/** State helpers */

Wyświetl plik

@ -17,7 +17,8 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>`;
(function () {
/** Initialize the Datasette startup! */
const initDatasetteTable = function (manager) {
// Feature detection
if (!window.URLSearchParams) {
return;
@ -68,13 +69,11 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
menu.style.display = "none";
menu.classList.remove("anim-scale-in");
}
// When page loads, add scroll listener on .table-wrapper
document.addEventListener("DOMContentLoaded", () => {
var tableWrapper = document.querySelector(".table-wrapper");
if (tableWrapper) {
tableWrapper.addEventListener("scroll", closeMenu);
}
});
const tableWrapper = document.querySelector(".table-wrapper");
if (tableWrapper) {
tableWrapper.addEventListener("scroll", closeMenu);
}
document.body.addEventListener("click", (ev) => {
/* was this click outside the menu? */
var target = ev.target;
@ -85,7 +84,9 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
closeMenu();
}
});
function iconClicked(ev) {
/** Table header */
function onTableHeaderClick(ev) {
ev.preventDefault();
ev.stopPropagation();
var th = ev.target;
@ -185,6 +186,33 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
menu.style.left = menuLeft + "px";
menu.style.display = "block";
menu.classList.add("anim-scale-in");
// Custom menu items on each render
// Plugin hook: allow adding JS-based additional menu items
const menuList = menu.querySelector('ul');
const columnItemConfigs = manager.getColumnHeaderItems(column);
columnItemConfigs.forEach(itemConfig => {
// Remove items from previous render. We assume entries have unique labels.
const existingItems = menuList.querySelectorAll(`li`);
Array.from(existingItems).filter(item => item.innerText === itemConfig.label).forEach(node => {
node.remove();
});
// Create a new items for each config.
const newLink = document.createElement('a');
newLink.textContent = itemConfig.label;
newLink.href = itemConfig.href ?? '#';
if (itemConfig.onClick) {
newLink.onclick = itemConfig.onClick(ev);
}
// Attach new elements to DOM
const menuItem = document.createElement('li');
menuItem.appendChild(newLink);
menuList.appendChild(menuItem);
});
}
var svg = document.createElement("div");
svg.innerHTML = DROPDOWN_ICON_SVG;
@ -203,13 +231,13 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
return;
}
var icon = svg.cloneNode(true);
icon.addEventListener("click", iconClicked);
icon.addEventListener("click", onTableHeaderClick);
th.appendChild(icon);
});
})();
};
/* Add x buttons to the filter rows */
(function () {
function addButtonsToFilterRows() {
var x = "✖";
var rows = Array.from(document.querySelectorAll(".filter-row")).filter((el) =>
el.querySelector(".filter-op")
@ -234,10 +262,10 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
a.style.display = "none";
}
});
})();
};
/* Set up datalist autocomplete for filter values */
(function () {
function initAutocompleteForFilterValues() {
function createDataLists() {
var facetResults = document.querySelectorAll(
".facet-results [data-column]"
@ -271,15 +299,51 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
.setAttribute("list", "datalist-" + event.target.value);
}
});
})();
};
// TODO: plugins need to wait for a custom event
// TODO: document what events they can listen for
document.addEventListener("InitDatasette", function (evt) {
// Manager is in event.detail
console.log(evt.detail);
evt.detail.registerPlugin('table', { version: 12, myCoolHello: () => console.log('hello') });
// console.log();
const {detail: manager} = evt;
// Main table
initDatasetteTable(manager);
// Other UI functions that need interactivity
addButtonsToFilterRows();
initAutocompleteForFilterValues();
// Then add your plugins
// Test plugins:
manager.registerPlugin('copy-column-name-to-clipboard', {
version: 0.1,
getColumnHeaderItems: (columnName) => {
return [{
label: "Copy name to clipboard",
onClick: (evt) => copyToClipboard(columnName)
}]
}
});
// Future: dispatch message to parent plugin with name of clicked column
// Could use to drive filter/sort etc query builder actions without a page refresh.
});
async function copyToClipboard(str) {
try {
await navigator.clipboard.writeText(str);
} catch (err) {
/* Rejected - text failed to copy to the clipboard. Browsers didn't give permission*/
console.error('Failed to copy: ', err);
}
}