mirror of
https://github.com/zotero/zotero.git
synced 2026-09-11 22:51:15 +00:00
Scaffold: UI tweaks and testing overhaul improvements (#5441)
- Remember diffs from test runs, show in sidebar when test is selected - Add button to immediately update test, without a prompt - Indicate when test has a custom defer delay set (although no tests currently do) - Use a persistent test store as source of truth, instead of data attributes on the listbox rows - A bit of code cleanup Closes #5420, closes #5419
This commit is contained in:
parent
e2fc3d35b8
commit
46f879dcdb
5 changed files with 333 additions and 192 deletions
|
|
@ -23,7 +23,6 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
var { E10SUtils } = ChromeUtils.importESModule("resource://gre/modules/E10SUtils.sys.mjs");
|
||||
var { Subprocess } = ChromeUtils.importESModule("resource://gre/modules/Subprocess.sys.mjs");
|
||||
var { RemoteTranslate } = ChromeUtils.importESModule("chrome://zotero/content/RemoteTranslate.mjs");
|
||||
|
||||
|
|
@ -65,6 +64,14 @@ var Scaffold = new function () {
|
|||
'textbox-hidden-prefs': 'hiddenPrefs'
|
||||
};
|
||||
|
||||
/** @type {{
|
||||
* test: Test;
|
||||
* testString: string;
|
||||
* updatedTestString?: string;
|
||||
* status?: string;
|
||||
* previews: ScaffoldItemPreview[];
|
||||
* }[]} */
|
||||
var _testData = [];
|
||||
var _linesOfMetadata = 15;
|
||||
|
||||
this.handleLoad = async function () {
|
||||
|
|
@ -497,9 +504,9 @@ var Scaffold = new function () {
|
|||
|
||||
let applyUpdatesCommand = editor.addCommand(
|
||||
0,
|
||||
async (_ctx, testIndices) => {
|
||||
(_ctx, testIndices) => {
|
||||
testIndices = testIndices || [..._loadTestsFromPane().keys()];
|
||||
await this.updateTests(testIndices);
|
||||
this.updateTests(testIndices);
|
||||
},
|
||||
'');
|
||||
|
||||
|
|
@ -511,9 +518,8 @@ var Scaffold = new function () {
|
|||
|
||||
provideCodeLenses: (model, _token) => {
|
||||
let testsWithUpdates = new Set();
|
||||
let testItems = Array.from(document.getElementById('testing-listbox').itemChildren);
|
||||
for (let [testIndex, itemChild] of testItems.entries()) {
|
||||
if (itemChild.dataset.updatedTestString) {
|
||||
for (let [testIndex, { updatedTestString }] of _testData.entries()) {
|
||||
if (updatedTestString) {
|
||||
testsWithUpdates.add(testIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -947,11 +953,17 @@ var Scaffold = new function () {
|
|||
else {
|
||||
codeTabBroadcaster.setAttribute('disabled', true);
|
||||
}
|
||||
|
||||
if (tab == 'tests') {
|
||||
this.handleTestingListboxSelect();
|
||||
}
|
||||
};
|
||||
|
||||
this.handleTestingContextMenuShowing = function () {
|
||||
let selectedItems = Array.from(document.getElementById('testing-listbox').selectedItems);
|
||||
let listbox = document.getElementById('testing-listbox');
|
||||
let selectedItems = Array.from(listbox.selectedItems);
|
||||
if (!selectedItems.length) return;
|
||||
let selectedIndices = selectedItems.map(item => listbox.getIndexOfItem(item));
|
||||
|
||||
let editImport = document.getElementById('testing-editImport');
|
||||
let openURL = document.getElementById('testing-openURL');
|
||||
|
|
@ -961,11 +973,23 @@ var Scaffold = new function () {
|
|||
openURL.disabled = true;
|
||||
}
|
||||
else {
|
||||
let selectedItem = selectedItems[0];
|
||||
editImport.disabled = selectedItem.dataset.testType === 'web';
|
||||
openURL.disabled = selectedItem.dataset.testType !== 'web';
|
||||
let { test } = _testData[selectedIndices[0]];
|
||||
editImport.disabled = test.type === 'web';
|
||||
openURL.disabled = test.type !== 'web';
|
||||
}
|
||||
applyUpdates.disabled = selectedItems.some(item => !item.dataset.updatedTestString);
|
||||
applyUpdates.disabled = selectedIndices.some(idx => !_testData[idx].updatedTestString);
|
||||
};
|
||||
|
||||
this.handleTestingListboxSelect = function () {
|
||||
let listbox = document.querySelector('#testing-listbox');
|
||||
let itemPreviews = document.querySelector('#item-previews');
|
||||
let previews = [];
|
||||
for (let item of listbox.selectedItems) {
|
||||
let index = listbox.getIndexOfItem(item);
|
||||
let testData = _testData[index];
|
||||
previews.push(...testData.previews);
|
||||
}
|
||||
itemPreviews.setPreviews(previews);
|
||||
};
|
||||
|
||||
this.handleTestingListboxDblClick = function () {
|
||||
|
|
@ -975,7 +999,6 @@ var Scaffold = new function () {
|
|||
// Add special keydown handling for the editors
|
||||
this.addEditorKeydownHandlers = function (editor) {
|
||||
let doc = editor.getDomNode().ownerDocument;
|
||||
let tabbox = document.getElementById("left-tabbox");
|
||||
// On shift-tab from the start of the first line, tab out of the editor.
|
||||
// Use capturing listener, since Shift-Tab keydown events do not propagate to the document.
|
||||
doc.addEventListener("keydown", (event) => {
|
||||
|
|
@ -1459,7 +1482,8 @@ var Scaffold = new function () {
|
|||
*/
|
||||
function _loadTestsFromPane() {
|
||||
try {
|
||||
return JSON.parse(_editors.tests.getValue().trim() || '[]');
|
||||
return JSON.parse(_editors.tests.getValue().trim() || '[]')
|
||||
.map(test => new Test(test));
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
|
|
@ -1494,6 +1518,10 @@ var Scaffold = new function () {
|
|||
* * Some fields, like those inside creator objects, notes, etc. are not sorted
|
||||
*/
|
||||
function _stringifyTests(value, level) {
|
||||
if (value instanceof Test) {
|
||||
value = value.toJSON();
|
||||
}
|
||||
|
||||
function processRow(key, value) {
|
||||
let val = _stringifyTests(value, level + 1);
|
||||
if (val === undefined) return undefined;
|
||||
|
|
@ -1609,9 +1637,9 @@ var Scaffold = new function () {
|
|||
}
|
||||
|
||||
_showTab('tests');
|
||||
let listBox = document.getElementById('testing-listbox');
|
||||
listBox.selectedIndex = listBox.getRowCount() - 1;
|
||||
listBox.focus();
|
||||
let listbox = document.getElementById('testing-listbox');
|
||||
listbox.selectedIndex = listbox.getRowCount() - 1;
|
||||
listbox.focus();
|
||||
};
|
||||
|
||||
this.constructTestFromCurrent = async function (type) {
|
||||
|
|
@ -1669,15 +1697,6 @@ var Scaffold = new function () {
|
|||
* populate tests pane and url options in browser pane
|
||||
*/
|
||||
this.populateTests = function () {
|
||||
function wrapWithHBox(elem, { flex, pack, width } = {}) {
|
||||
let hbox = document.createXULElement('hbox');
|
||||
hbox.append(elem);
|
||||
if (flex !== undefined) hbox.setAttribute('flex', flex);
|
||||
if (pack !== undefined) hbox.setAttribute('pack', pack);
|
||||
if (width !== undefined) hbox.style.width = width + 'px';
|
||||
return hbox;
|
||||
}
|
||||
|
||||
let tests = _loadTestsFromPane();
|
||||
let validateTestsBroadcaster = document.getElementById('validate-tests');
|
||||
if (tests === null) {
|
||||
|
|
@ -1688,77 +1707,108 @@ var Scaffold = new function () {
|
|||
validateTestsBroadcaster.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
let listBox = document.getElementById("testing-listbox");
|
||||
let count = listBox.getRowCount();
|
||||
let oldStatusNodes = {};
|
||||
for (let i = 0; i < count; i++) {
|
||||
let item = listBox.getItemAtIndex(i);
|
||||
oldStatusNodes[item.dataset.testString] = item.querySelector('.status');
|
||||
}
|
||||
let listbox = document.getElementById("testing-listbox");
|
||||
|
||||
while (listBox.itemChildren.length > tests.length) {
|
||||
listBox.itemChildren[listBox.itemChildren.length - 1].remove();
|
||||
}
|
||||
for (let [testIndex, test] of tests.entries()) {
|
||||
let testString = _stringifyTests(test, 1);
|
||||
let testData = _testData[testIndex];
|
||||
if (!testData || testData.testString !== testString) {
|
||||
_testData[testIndex] = testData = {
|
||||
test,
|
||||
testString,
|
||||
previews: [],
|
||||
};
|
||||
}
|
||||
testData.test = test;
|
||||
testData.testString = testString;
|
||||
// Remove updatedTestString if updates have been applied
|
||||
if (testData.updatedTestString === testString) {
|
||||
delete testData.updatedTestString;
|
||||
}
|
||||
let needsUpdate = !!testData.updatedTestString;
|
||||
|
||||
// try to reuse old rows
|
||||
let item = testIndex < count
|
||||
? listBox.getItemAtIndex(testIndex)
|
||||
let item = testIndex < listbox.getRowCount()
|
||||
? listbox.getItemAtIndex(testIndex)
|
||||
: document.createXULElement('richlistitem');
|
||||
|
||||
item.replaceChildren();
|
||||
|
||||
let inputCell = document.createXULElement('hbox');
|
||||
inputCell.classList.add('cell', 'col-input');
|
||||
|
||||
let input = document.createXULElement('label');
|
||||
input.append(getTestLabel(test));
|
||||
item.appendChild(wrapWithHBox(input, { flex: 1 }));
|
||||
inputCell.append(input);
|
||||
item.append(inputCell);
|
||||
|
||||
let status = oldStatusNodes[testString];
|
||||
if (!status) {
|
||||
status = document.createXULElement('label');
|
||||
status.classList.add('status');
|
||||
status.addEventListener('click', (event) => {
|
||||
if (!status.classList.contains('needs-update')) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
if (!Services.prompt.confirm(
|
||||
null,
|
||||
'Scaffold',
|
||||
`Apply updates to this test?`
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
this.updateTests([testIndex]);
|
||||
});
|
||||
let statusCell = document.createXULElement('hbox');
|
||||
statusCell.classList.add('cell', 'col-status');
|
||||
statusCell.classList.toggle('needs-update', needsUpdate);
|
||||
let statusLabel = document.createXULElement('label');
|
||||
statusLabel.classList.add('status');
|
||||
|
||||
let statusText = _testData.find(test => test.testString === testString)?.status ?? '';
|
||||
if (needsUpdate) {
|
||||
let totalStats = { added: 0, removed: 0 };
|
||||
for (let preview of testData.previews) {
|
||||
let statsHere = preview.diffStats;
|
||||
totalStats.added += statsHere.added;
|
||||
totalStats.removed += statsHere.removed;
|
||||
}
|
||||
|
||||
let textElem = document.createElement('span');
|
||||
textElem.classList.add('text');
|
||||
textElem.textContent = statusText;
|
||||
let addedElem = document.createElement('span');
|
||||
addedElem.classList.add('added');
|
||||
addedElem.textContent = `+${totalStats.added}`;
|
||||
let removedElem = document.createElement('span');
|
||||
removedElem.classList.add('removed');
|
||||
removedElem.textContent = `-${totalStats.removed}`;
|
||||
|
||||
statusLabel.replaceChildren(textElem, addedElem, removedElem);
|
||||
}
|
||||
else {
|
||||
statusLabel.textContent = statusText;
|
||||
}
|
||||
item.appendChild(wrapWithHBox(status, { width: 150 }));
|
||||
|
||||
statusCell.append(statusLabel);
|
||||
let updateButton = document.createXULElement('toolbarbutton');
|
||||
updateButton.classList.add('update');
|
||||
updateButton.setAttribute('tooltiptext', 'Update Test');
|
||||
updateButton.addEventListener('command', () => this.updateTests([testIndex]));
|
||||
statusCell.append(updateButton);
|
||||
item.append(statusCell);
|
||||
|
||||
let deferCell = document.createXULElement('hbox');
|
||||
deferCell.classList.add('cell', 'col-defer');
|
||||
let defer = document.createXULElement('checkbox');
|
||||
defer.checked = test.defer;
|
||||
if (typeof test.defer === 'number' && test.defer) {
|
||||
defer.setAttribute('indeterminate', 'true');
|
||||
}
|
||||
else {
|
||||
defer.checked = test.defer;
|
||||
}
|
||||
defer.setAttribute('native', 'true');
|
||||
defer.addEventListener('command', () => {
|
||||
if (defer.checked) {
|
||||
test.defer = true;
|
||||
}
|
||||
else {
|
||||
delete test.defer;
|
||||
}
|
||||
test.defer = defer.checked;
|
||||
_writeTestsToPane(tests);
|
||||
});
|
||||
item.appendChild(wrapWithHBox(defer, { pack: 'center', width: 75 }));
|
||||
deferCell.append(defer);
|
||||
item.append(deferCell);
|
||||
|
||||
// Remove updatedTestString if the test changed or updates have been applied
|
||||
if (item.dataset.testString !== testString || item.dataset.updatedTestString === testString) {
|
||||
delete item.dataset.updatedTestString;
|
||||
}
|
||||
item.dataset.testString = testString;
|
||||
item.dataset.testType = test.type;
|
||||
|
||||
if (testIndex >= count) {
|
||||
listBox.appendChild(item);
|
||||
if (!item.parentElement) {
|
||||
listbox.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused _testData entries and listbox rows
|
||||
_testData = _testData.slice(0, tests.length);
|
||||
while (listbox.getRowCount() > tests.length) {
|
||||
listbox.getItemAtIndex(listbox.getRowCount() - 1).remove();
|
||||
}
|
||||
|
||||
// Update UI that depends on the selection
|
||||
this.handleTestingListboxSelect();
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -1781,8 +1831,7 @@ var Scaffold = new function () {
|
|||
*/
|
||||
this.editImportFromTest = function () {
|
||||
var listbox = document.getElementById("testing-listbox");
|
||||
var item = listbox.selectedItems[0];
|
||||
var test = JSON.parse(item.dataset.testString);
|
||||
var { test } = _testData[listbox.selectedIndex];
|
||||
if (test.input === undefined) {
|
||||
_logOutput("Can't edit input of a non-import/search test.");
|
||||
}
|
||||
|
|
@ -1804,14 +1853,12 @@ var Scaffold = new function () {
|
|||
*/
|
||||
this.copyToClipboard = function () {
|
||||
var listbox = document.getElementById("testing-listbox");
|
||||
var item = listbox.selectedItems[0];
|
||||
var url = item.getElementsByTagName("label")[0].textContent;
|
||||
var test = JSON.parse(item.dataset.testString);
|
||||
var urlOrData = (test.input !== undefined) ? test.input : url;
|
||||
if (typeof urlOrData !== 'string') {
|
||||
urlOrData = JSON.stringify(urlOrData, null, '\t');
|
||||
var { test } = _testData[listbox.selectedIndex];
|
||||
var input = test.input; // URL or input object
|
||||
if (typeof input !== 'string') {
|
||||
input = JSON.stringify(input, null, '\t');
|
||||
}
|
||||
Zotero.Utilities.Internal.copyTextToClipboard(urlOrData);
|
||||
Zotero.Utilities.Internal.copyTextToClipboard(input);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1821,8 +1868,8 @@ var Scaffold = new function () {
|
|||
**/
|
||||
this.openURL = function (openExternally) {
|
||||
var listbox = document.getElementById("testing-listbox");
|
||||
var item = listbox.selectedItems[0];
|
||||
var url = item.getElementsByTagName("label")[0].textContent;
|
||||
var { test } = _testData[listbox.selectedIndex];
|
||||
var url = test.url;
|
||||
if (openExternally) {
|
||||
Zotero.launchURL(url);
|
||||
}
|
||||
|
|
@ -1835,28 +1882,23 @@ var Scaffold = new function () {
|
|||
};
|
||||
|
||||
this.runTests = async function (testIndices) {
|
||||
let listbox = document.getElementById('testing-listbox');
|
||||
let items = [...listbox.itemChildren];
|
||||
let itemsToUpdate = testIndices.map(index => items[index]);
|
||||
|
||||
let itemPreviews = document.querySelector('#item-previews');
|
||||
let testDatas = testIndices.map(index => _testData[index]);
|
||||
|
||||
let tests = [];
|
||||
for (let listItem of itemsToUpdate) {
|
||||
listItem.querySelector('.status').textContent = 'Running';
|
||||
|
||||
let test = new Test(JSON.parse(listItem.dataset.testString));
|
||||
tests.push(test);
|
||||
for (let testData of testDatas) {
|
||||
testData.status = 'Running';
|
||||
delete testData.updatedTestString;
|
||||
}
|
||||
this.populateTests();
|
||||
|
||||
let numTests = tests.length;
|
||||
let currentTest = 1; // For logging
|
||||
let numTests = testDatas.length;
|
||||
let testIndex = 0;
|
||||
_logOutput(`Running ${numTests} ${Zotero.Utilities.pluralize(numTests, 'test')}`);
|
||||
for await (let { test, status, reason, updatedTest } of this.runTestsInternal(tests)) {
|
||||
for await (let { test, status, reason, updatedTest } of this.runTestsInternal(testDatas.map(d => d.test))) {
|
||||
let statusText;
|
||||
let needsUpdate = false;
|
||||
|
||||
let logPrefix = `Test ${currentTest}/${numTests}: `;
|
||||
let logPrefix = `Test ${testIndex + 1}/${numTests}: `;
|
||||
if (status === 'success') {
|
||||
statusText = 'Succeeded';
|
||||
_logOutput(logPrefix + statusText);
|
||||
|
|
@ -1867,45 +1909,29 @@ var Scaffold = new function () {
|
|||
_logOutput(logPrefix + statusText);
|
||||
}
|
||||
|
||||
// Show the preview pane as long as we got new item data,
|
||||
// Create previews as long as we got new item data,
|
||||
// even if there weren't substantive changes
|
||||
let totalStats = { added: 0, removed: 0 };
|
||||
let previews = [];
|
||||
if (Array.isArray(test.items) && Array.isArray(updatedTest?.items)) {
|
||||
for (let i = 0; i < test.items.length || i < updatedTest.items.length; i++) {
|
||||
let preItem = i < test.items.length ? test.items[i] : {};
|
||||
let postItem = i < updatedTest.items.length ? updatedTest.items[i] : {};
|
||||
|
||||
let preview = itemPreviews.addItemPair(preItem, postItem);
|
||||
let statsHere = preview.diffStats;
|
||||
totalStats.added += statsHere.added;
|
||||
totalStats.removed += statsHere.removed;
|
||||
previews.push(itemPreviews.createPreviewForItemPair(preItem, postItem));
|
||||
}
|
||||
}
|
||||
|
||||
let listItem = itemsToUpdate[tests.indexOf(test)];
|
||||
let statusLabel = listItem.querySelector('.status');
|
||||
statusLabel.classList.toggle('needs-update', needsUpdate);
|
||||
|
||||
let textElem = document.createElement('span');
|
||||
textElem.classList.add('text');
|
||||
textElem.textContent = statusText;
|
||||
let addedElem = document.createElement('span');
|
||||
addedElem.classList.add('added');
|
||||
addedElem.textContent = `+${totalStats.added}`;
|
||||
let removedElem = document.createElement('span');
|
||||
removedElem.classList.add('removed');
|
||||
removedElem.textContent = `-${totalStats.removed}`;
|
||||
|
||||
statusLabel.replaceChildren(textElem, addedElem, removedElem);
|
||||
|
||||
let testData = testDatas[testIndex];
|
||||
testData.status = statusText;
|
||||
testData.previews = previews;
|
||||
if (needsUpdate) {
|
||||
listItem.dataset.updatedTestString = _stringifyTests(updatedTest.toJSON(), 1);
|
||||
testData.updatedTestString = _stringifyTests(updatedTest.toJSON(), 1);
|
||||
}
|
||||
else {
|
||||
delete listItem.dataset.updatedTestString;
|
||||
delete testData.updatedTestString;
|
||||
}
|
||||
this.populateTests();
|
||||
|
||||
currentTest++;
|
||||
testIndex++;
|
||||
}
|
||||
|
||||
_invalidateCodeLenses?.();
|
||||
|
|
@ -1935,24 +1961,21 @@ var Scaffold = new function () {
|
|||
}
|
||||
};
|
||||
|
||||
this.updateTests = async function (testIndices) {
|
||||
let listbox = document.getElementById('testing-listbox');
|
||||
let items = [...listbox.itemChildren];
|
||||
let itemsToUpdate = testIndices.map(index => items[index]);
|
||||
|
||||
this.updateTests = function (testIndices) {
|
||||
let tests = _loadTestsFromPane();
|
||||
for (let item of itemsToUpdate) {
|
||||
let updatedTestString = item.dataset.updatedTestString;
|
||||
for (let testIndex of testIndices) {
|
||||
let testData = _testData[testIndex];
|
||||
let updatedTestString = testData.updatedTestString;
|
||||
if (!updatedTestString) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let updatedTest = JSON.parse(updatedTestString);
|
||||
tests[items.indexOf(item)] = updatedTest;
|
||||
item.dataset.testString = _stringifyTests(updatedTest, 1);
|
||||
let status = item.querySelector('.status');
|
||||
status.textContent = 'Updated';
|
||||
status.classList.remove('needs-update');
|
||||
tests[testIndex] = updatedTest;
|
||||
testData.test = new Test(updatedTest);
|
||||
testData.testString = _stringifyTests(updatedTest, 1);
|
||||
delete testData.updatedTestString;
|
||||
testData.status = 'Updated';
|
||||
}
|
||||
_writeTestsToPane(tests);
|
||||
_logOutput('Tests updated.');
|
||||
|
|
@ -1961,7 +1984,7 @@ var Scaffold = new function () {
|
|||
this.updateSelectedTests = async function () {
|
||||
let listbox = document.getElementById('testing-listbox');
|
||||
let selectedItems = [...listbox.selectedItems];
|
||||
await this.updateTests(
|
||||
this.updateTests(
|
||||
[...selectedItems].map(item => listbox.getIndexOfItem(item))
|
||||
);
|
||||
};
|
||||
|
|
@ -2003,7 +2026,7 @@ var Scaffold = new function () {
|
|||
function _clearOutput() {
|
||||
document.getElementById('output').value = '';
|
||||
let itemPreviews = document.querySelector('#item-previews');
|
||||
itemPreviews.clearItemPairs();
|
||||
itemPreviews.clearPreviews();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -482,9 +482,9 @@
|
|||
</keyset>
|
||||
|
||||
<listheader>
|
||||
<treecol label="&scaffold.testing.input.label;" flex="1"/>
|
||||
<treecol label="&scaffold.testing.status.label;" width="150"/>
|
||||
<treecol label="&scaffold.testing.defer.label;" width="75"/>
|
||||
<treecol label="&scaffold.testing.input.label;" class="col-input"/>
|
||||
<treecol label="&scaffold.testing.status.label;" class="col-status"/>
|
||||
<treecol label="&scaffold.testing.defer.label;" class="col-defer"/>
|
||||
</listheader>
|
||||
<richlistbox
|
||||
id="testing-listbox"
|
||||
|
|
@ -493,6 +493,7 @@
|
|||
seltype="multiple"
|
||||
context="testing-context-menu"
|
||||
focus-on-tab-select="true"
|
||||
onselect="Scaffold.handleTestingListboxSelect()"
|
||||
ondblclick="Scaffold.handleTestingListboxDblClick()"
|
||||
/>
|
||||
</vbox>
|
||||
|
|
|
|||
|
|
@ -35,34 +35,62 @@
|
|||
</html:div>
|
||||
<deck/>
|
||||
`);
|
||||
|
||||
_previews = [];
|
||||
|
||||
_deck;
|
||||
|
||||
_switcher;
|
||||
|
||||
_itemPairs = [];
|
||||
|
||||
init() {
|
||||
this._deck = this.querySelector('deck');
|
||||
this._switcher = this.querySelector('.switcher');
|
||||
|
||||
this.querySelector('.previous').addEventListener('command', () => {
|
||||
this._deck.selectedIndex--;
|
||||
this._renderSwitcher();
|
||||
this._renderPreviews();
|
||||
});
|
||||
this.querySelector('.next').addEventListener('command', () => {
|
||||
this._deck.selectedIndex++;
|
||||
this._renderSwitcher();
|
||||
this._renderPreviews();
|
||||
});
|
||||
}
|
||||
|
||||
clearItemPairs() {
|
||||
this._itemPairs = [];
|
||||
/**
|
||||
* @param {any} preItem JSON item
|
||||
* @param {any} [postItem] JSON item (defaults to cleaned version of preItem)
|
||||
* @returns {ScaffoldItemPreview}
|
||||
*/
|
||||
createPreviewForItemPair(preItem, postItem) {
|
||||
let preview = document.createXULElement('scaffold-item-preview');
|
||||
preview.itemPair = [preItem, postItem];
|
||||
return preview;
|
||||
}
|
||||
|
||||
clearPreviews() {
|
||||
this._previews = [];
|
||||
this._deck.selectedIndex = 0;
|
||||
this._deck.replaceChildren();
|
||||
this._renderSwitcher();
|
||||
|
||||
this.hidden = true;
|
||||
this._updateVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ScaffoldItemPreview[]} previews
|
||||
*/
|
||||
setPreviews(previews) {
|
||||
this._previews = previews;
|
||||
this._deck.selectedIndex = 0;
|
||||
this._renderPreviews();
|
||||
this._updateVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ScaffoldItemPreview} preview
|
||||
*/
|
||||
addPreview(preview) {
|
||||
this._previews.push(preview);
|
||||
this._renderPreviews();
|
||||
this._updateVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,33 +99,45 @@
|
|||
* @returns {ScaffoldItemPreview}
|
||||
*/
|
||||
addItemPair(preItem, postItem) {
|
||||
let preview = document.createXULElement('scaffold-item-preview');
|
||||
preview.itemPair = [preItem, postItem];
|
||||
this._deck.append(preview);
|
||||
this._renderSwitcher();
|
||||
|
||||
if (this.hidden) {
|
||||
let splitterPane = this.closest('splitter + *');
|
||||
if (splitterPane) {
|
||||
// If the pane hasn't been resized, it won't have a fixed width,
|
||||
// so it'll grow when wrapping text is added. Fix its width now.
|
||||
splitterPane.style.width = splitterPane.getBoundingClientRect().width + 'px';
|
||||
}
|
||||
this.hidden = false;
|
||||
}
|
||||
|
||||
let preview = this.createPreviewForItemPair(preItem, postItem);
|
||||
this.addPreview(preview);
|
||||
return preview;
|
||||
}
|
||||
|
||||
_renderSwitcher() {
|
||||
let switcher = this.querySelector('.switcher');
|
||||
if (this._deck.children.length > 1) {
|
||||
switcher.hidden = false;
|
||||
switcher.querySelector('.current').textContent = this._deck.selectedIndex + 1;
|
||||
switcher.querySelector('.max').textContent = this._deck.children.length;
|
||||
_updateVisibility() {
|
||||
if (this._deck.childElementCount) {
|
||||
if (this.hidden) {
|
||||
let splitterPane = this.closest('splitter + *');
|
||||
if (splitterPane) {
|
||||
// If the pane hasn't been resized, it won't have a fixed width,
|
||||
// so it'll grow when wrapping text is added. Fix its width now.
|
||||
splitterPane.style.width = splitterPane.getBoundingClientRect().width + 'px';
|
||||
}
|
||||
this.hidden = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switcher.hidden = true;
|
||||
this.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
_renderPreviews() {
|
||||
this._deck.replaceChildren(
|
||||
...this._previews.map((preview, i) => {
|
||||
if (i === this._deck.selectedIndex) {
|
||||
return preview;
|
||||
}
|
||||
return document.createXULElement('hbox');
|
||||
})
|
||||
);
|
||||
|
||||
if (this._deck.children.length > 1) {
|
||||
this._switcher.hidden = false;
|
||||
this._switcher.querySelector('.current').textContent = this._deck.selectedIndex + 1;
|
||||
this._switcher.querySelector('.max').textContent = this._deck.children.length;
|
||||
}
|
||||
else {
|
||||
this._switcher.hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
chrome/skin/default/zotero/16/universal/sync-12.svg
Normal file
5
chrome/skin/default/zotero/16/universal/sync-12.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="M11.9156 7C11.4395 9.8374 8.97322 11.9997 6.00058 12C3.91218 12 2.0741 10.9321 0.999603 9.31348V12H-0.000396729V8H3.9996V9H2.00546C2.91757 10.2131 4.36592 11 6.00058 11C8.41927 10.9997 10.4367 9.28204 10.9 7H11.9156ZM11.9996 4H7.9996V3H9.99472C9.08262 1.78682 7.6343 1 5.9996 1C3.58083 1.00019 1.56345 2.7179 1.10019 5H0.0845642C0.560729 2.16254 3.02688 0.000194025 5.9996 0C8.08726 0 9.92494 1.06689 10.9996 2.68457V0H11.9996V4Z" fill="context-fill"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 574 B |
|
|
@ -184,6 +184,30 @@ browser,
|
|||
max-width: 100%;
|
||||
gap: 8px;
|
||||
|
||||
listheader, richlistbox {
|
||||
.col-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.col-status {
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
.col-defer {
|
||||
width: 60px;
|
||||
|
||||
& {
|
||||
margin-inline: 0;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.treecol-text {
|
||||
margin-inline-end: 0 !important;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listheader {
|
||||
// Keep aligned with item columns when scrollbars are visible
|
||||
scrollbar-gutter: stable;
|
||||
|
|
@ -202,25 +226,34 @@ browser,
|
|||
background: var(--material-stripe);
|
||||
}
|
||||
|
||||
.status {
|
||||
.cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
&:not(.needs-update) {
|
||||
.added, .removed {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 5px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
&.needs-update {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cell.col-status {
|
||||
.status {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
width: unset;
|
||||
flex: 1;
|
||||
|
||||
.text {
|
||||
color: LinkText;
|
||||
text-decoration: underline;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.added, .removed {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.added {
|
||||
color: rgb(40 161 40 / 0.8);
|
||||
}
|
||||
|
|
@ -229,20 +262,58 @@ browser,
|
|||
color: rgb(210 50 50 / 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.update {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
@include svgicon-menu('sync-12', 'universal', '16');
|
||||
|
||||
.toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.needs-update) {
|
||||
.status {
|
||||
.added, .removed {
|
||||
color: revert;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.update {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cell.col-defer {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus richlistitem[selected] {
|
||||
.status {
|
||||
&.needs-update {
|
||||
.text {
|
||||
color: var(--color-accent-text);
|
||||
}
|
||||
|
||||
.cell.col-status.needs-update {
|
||||
.status {
|
||||
.added, .removed {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.update {
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
background: #ffffff1a;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #ffffff33;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -361,6 +432,7 @@ scaffold-item-preview {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-block: 6px;
|
||||
gap: 6px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue