Added Header Aliases

This commit is contained in:
Michael Rowley 2022-08-13 12:16:21 +01:00
parent 037590f831
commit da7cd1668a
3 changed files with 175 additions and 21 deletions

View file

@ -167,6 +167,8 @@ class Manager {
document.getElementById("btn-go-to-input-tab").addEventListener("click", this.input.goToTab.bind(this.input)); document.getElementById("btn-go-to-input-tab").addEventListener("click", this.input.goToTab.bind(this.input));
document.getElementById("btn-find-input-tab").addEventListener("click", this.input.findTab.bind(this.input)); document.getElementById("btn-find-input-tab").addEventListener("click", this.input.findTab.bind(this.input));
this.addDynamicListener("#input-tabs li .input-tab-content", "click", this.input.changeTabClick, this.input); this.addDynamicListener("#input-tabs li .input-tab-content", "click", this.input.changeTabClick, this.input);
this.addDynamicListener("#input-tabs-wrapper #input-tabs .input-tab-content", "dblclick", this.input.renameTabClick, this.input);
this.addDynamicListener("#input-tabs-wrapper #input-tabs .input-tab-content span input", "focusout", this.input.confirmTabRename, this.input);
document.getElementById("input-show-pending").addEventListener("change", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-show-pending").addEventListener("change", this.input.filterTabSearch.bind(this.input));
document.getElementById("input-show-loading").addEventListener("change", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-show-loading").addEventListener("change", this.input.filterTabSearch.bind(this.input));
document.getElementById("input-show-loaded").addEventListener("change", this.input.filterTabSearch.bind(this.input)); document.getElementById("input-show-loaded").addEventListener("change", this.input.filterTabSearch.bind(this.input));

View file

@ -1001,6 +1001,11 @@ class InputWaiter {
*/ */
changeTab(inputNum, changeOutput) { changeTab(inputNum, changeOutput) {
if (this.manager.tabs.getInputTabItem(inputNum) !== null) { if (this.manager.tabs.getInputTabItem(inputNum) !== null) {
if (this.manager.tabs.getActiveTab("input") === inputNum) {
if (changeOutput && this.manager.tabs.getActiveTab("output") !== inputNum)
this.manager.output.changeTab(inputNum, false);
return;
}
this.manager.tabs.changeInputTab(inputNum); this.manager.tabs.changeInputTab(inputNum);
this.inputWorker.postMessage({ this.inputWorker.postMessage({
action: "setInput", action: "setInput",
@ -1090,6 +1095,7 @@ class InputWaiter {
const inputNum = this.manager.tabs.getActiveInputTab(); const inputNum = this.manager.tabs.getActiveInputTab();
if (inputNum === -1) return; if (inputNum === -1) return;
this.manager.tabs.removeTabHeaderAlias(inputNum);
this.manager.highlighter.removeHighlights(); this.manager.highlighter.removeHighlights();
getSelection().removeAllRanges(); getSelection().removeAllRanges();
@ -1228,6 +1234,7 @@ class InputWaiter {
removeChefWorker: true removeChefWorker: true
} }
}); });
this.manager.tabs.removeTabHeaderAlias(inputNum);
this.manager.output.removeTab(inputNum); this.manager.output.removeTab(inputNum);
} }
@ -1241,9 +1248,10 @@ class InputWaiter {
if (!mouseEvent.target) { if (!mouseEvent.target) {
return; return;
} }
const tabNum = mouseEvent.target.closest("button").parentElement.getAttribute("inputNum"); const tabNumStr = mouseEvent.target.closest("button").parentElement.getAttribute("inputNum");
if (tabNum) { if (tabNumStr) {
this.removeInput(parseInt(tabNum, 10)); const tabNum = parseInt(tabNumStr, 10);
this.removeInput(tabNum);
} }
} }
@ -1438,6 +1446,74 @@ class InputWaiter {
this.app.updateTitle(urlData.includeInput, urlData.input, true); this.app.updateTitle(urlData.includeInput, urlData.input, true);
} }
/**
* Handler for renaming tabs.
* Opens the tab-renaming dialogue.
*
* @param {event} mouseEvent - The mouse event that this call was triggered by
*/
async renameTabClick(mouseEvent) {
const targetElement = mouseEvent.target;
if (!targetElement) return;
const editingElement = document.createElement("input");
editingElement.classList.add("form-control");
let renameContents = targetElement.textContent;
const renameContentsColon = renameContents.indexOf(":");
// Calling 'getInputValue()' might take a long time for large datasets,
// it could be beneficial to modify the API to allow for querying whether
// there is any input rather than getting the full string just to access its length.
const inputLength = (await this.getInputValue(this.manager.tabs.getActiveInputTab())).length;
// Remove the data from the renaming section
if (renameContentsColon !== -1 && inputLength !== 0) {
renameContents = renameContents.substring(0, renameContentsColon);
}
// Remove the single quotation marks from the renaming section
renameContents = renameContents.replaceAll("'", "");
if (renameContents.length < 3 && !isNaN(parseInt(renameContents, 10))) {
renameContents = `Tab ${renameContents.toString()}`;
}
editingElement.setAttribute("value", renameContents);
editingElement.setAttribute("minlength", "3"); // Delimiting between shortened tab headers and custom ones.
targetElement.textContent = "";
editingElement.style.height = "1.5em";
editingElement.style.textAlign = "center";
targetElement.appendChild(editingElement);
// A delay is required between appending and focusing.
await new Promise(r => setTimeout(r, 100));
editingElement.focus();
}
/**
* Assigns the current user-entered text to
* the tab's new name.
* Resets the DOM of the tab to the default non-editable state.
*/
async confirmTabRename() {
const activeInputTabNum = this.manager.tabs.getActiveInputTab();
if (activeInputTabNum === -1) {
return;
}
const activeInputTabElement = this.manager.tabs.getTabItem(activeInputTabNum, "input");
if (activeInputTabElement == null || activeInputTabElement.children.size < 1) {
return;
}
const tabContent = activeInputTabElement.children[0];
const tabHeader = tabContent.children[0].children[0].value;
const inputContents = await this.getInputValue(activeInputTabNum);
if (tabHeader.length === 0)
this.manager.tabs.removeTabHeaderAlias(activeInputTabNum, false);
else
this.manager.tabs.addTabHeaderAlias(activeInputTabNum, tabHeader);
this.manager.tabs.updateInputTabHeader(activeInputTabNum, inputContents);
this.manager.tabs.updateOutputTabHeader(activeInputTabNum, inputContents);
}
} }

View file

@ -18,6 +18,7 @@ class TabWaiter {
constructor(app, manager) { constructor(app, manager) {
this.app = app; this.app = app;
this.manager = manager; this.manager = manager;
this.tabHeaderAliases = []; // Mapping custom tab headers to indexes/numbers.
} }
/** /**
@ -251,21 +252,20 @@ class TabWaiter {
tabsList.appendChild(this.createTabElement(nums[i], active, io)); tabsList.appendChild(this.createTabElement(nums[i], active, io));
} }
// Display shadows if there are tabs left / right of the displayed tabs
if (tabsLeft) {
tabsList.classList.add("tabs-left");
} else {
tabsList.classList.remove("tabs-left");
}
if (tabsRight) {
tabsList.classList.add("tabs-right");
} else {
tabsList.classList.remove("tabs-right");
}
// Show or hide the tab bar depending on how many tabs we have // Show or hide the tab bar depending on how many tabs we have
if (nums.length > 1) { if (nums.length > 1) {
this.showTabBar(); this.showTabBar();
// Display shadows if there are tabs left / right of the displayed tabs
if (tabsLeft) {
tabsList.classList.add("tabs-left");
} else {
tabsList.classList.remove("tabs-left");
}
if (tabsRight) {
tabsList.classList.add("tabs-right");
} else {
tabsList.classList.remove("tabs-right");
}
} else { } else {
this.hideTabBar(); this.hideTabBar();
} }
@ -349,16 +349,35 @@ class TabWaiter {
* @param {string} data - The data to display in the tab header * @param {string} data - The data to display in the tab header
* @param {string} io - Either "input" or "output" * @param {string} io - Either "input" or "output"
*/ */
updateTabHeader(inputNum, data, io) { async updateTabHeader(inputNum, data, io) {
const tab = this.getTabItem(inputNum, io); const tab = this.getTabItem(inputNum, io);
if (tab === null) return; if (tab == null) return;
let headerData = `Tab ${inputNum}`; const customHeaderData = this.getTabHeaderAlias(inputNum);
if (data.length > 0) {
headerData = data.slice(0, 100); // When 'customHeaderData === `Tab ${inputNum}`' is true, it's usually due to
headerData = `${inputNum}: ${headerData}`; // a user having opened the rename textbox but then closing it without change.
const isStandardHeader = customHeaderData === null || customHeaderData === `Tab ${inputNum}`;
let headerData = isStandardHeader ? `Tab ${inputNum}` : customHeaderData;
const dataIsFile = data instanceof ArrayBuffer;
const includeData = data.length > 0 || dataIsFile;
if (includeData) {
const inputObj = await this.manager.input.getInputObj(inputNum);
const dataPreview = dataIsFile ? inputObj.data.name : data.slice(0, 100);
if (isStandardHeader)
headerData = inputNum.toString();
else
headerData = `'${customHeaderData}'`;
headerData += `: ${dataPreview}`;
} }
tab.firstElementChild.innerText = headerData; tab.firstElementChild.innerText = headerData;
if (!isStandardHeader && !includeData)
tab.firstElementChild.innerText = `'${headerData}'`;
} }
/** /**
@ -423,6 +442,63 @@ class TabWaiter {
this.updateTabProgress(inputNum, progress, total, "output"); this.updateTabProgress(inputNum, progress, total, "output");
} }
/**
* Adds an alias between a custom tab header and a tab number so that
* mapping between the two is possible if DOM element is removed.
*
* @param {number} tabNum - The index of the tab being aliased
* @param {string} tabHeader - The custom tab header
*/
addTabHeaderAlias(tabNum, tabHeader) {
// First, we try to overwrite an existing alias.
for (let i = 0; i < this.tabHeaderAliases.length; i++) {
if (this.tabHeaderAliases.at(i).tabNumber === tabNum) {
this.tabHeaderAliases.at(i).customHeader = tabHeader;
return;
}
}
this.tabHeaderAliases.push({tabNumber: tabNum, customHeader: tabHeader});
}
/**
* Removes a previously-assigned header alias.
*
* @param {number} tabNum - The index of the tab that should be removed.
* @param {boolean} shouldThrow - A boolean representing whether the function should throw an exception or return silently if it cannot locate the tab header.
*/
removeTabHeaderAlias(tabNum, shouldThrow) {
for (let i = 0; i < this.tabHeaderAliases.length; i++) {
if (this.tabHeaderAliases.at(i).tabNumber === tabNum) {
this.tabHeaderAliases.splice(i, 1);
return;
}
}
if (shouldThrow)
throw `Unable to locate header alias at tab index ${tabNum.toString()}.`;
}
/**
* Retrieves the custom header for a given tab.
*
* @param {number} tabNum - The index of the tab whose alias should be retrieved.
* @param {boolean} shouldThrow - Whether the function should throw an exception (instead of returning null) in the event of it being unable to locate the tab.
* @returns {string} customHeader - The custom header for the requested tab.
*/
getTabHeaderAlias(tabNum, shouldThrow) {
for (let i = 0; i < this.tabHeaderAliases.length; i++) {
if (this.tabHeaderAliases.at(i).tabNumber === tabNum)
return this.tabHeaderAliases.at(i).customHeader;
}
if (shouldThrow)
throw `Unable to locate header alias at tab index ${tabNum.toString()}.`;
return null;
}
} }
export default TabWaiter; export default TabWaiter;