use Icons.getCSSIcon instead of getDOMElement (#4338)

- getDOMElement relied on React.renderToStaticMarkup,
which is react 18 was moved to a different file than the
one exposed with react-dom-server. To not add another
file just for that one function, replace getDOMElement
with getCSSIcon.
- getDOMElement was mainly used for a few remaining
png icons that were not replaced with svg. For those
few icons, just record which background-url should be
set when the module loads and add it in getCSSIcon if
applicable. Alternatively, background-image setting
could be moved into a stylesheet?
- a few hardcoded twisty svgs in icons.jsx are not used anywhere
(they would be fetched via IconTwisty), so those are
removed
This commit is contained in:
Bogdan Abaev 2024-07-05 16:12:41 -07:00 committed by Dan Stillman
parent af4bbd2c4d
commit 37991e220e
5 changed files with 17 additions and 97 deletions

View file

@ -1,8 +1,6 @@
'use strict';
const React = require('react');
const { renderToStaticMarkup } = require('react-dom-server');
const { PureComponent } = React;
const { element, string, object } = require('prop-types');
const Icon = (props) => {
@ -51,6 +49,8 @@ CSSItemTypeIcon.propTypes = {
module.exports = { Icon, CSSIcon, CSSItemTypeIcon };
// Icons cache for a few remaining png icons till they are replaced
let legacyIconsCache = {};
function i(name, svgOrSrc, hasHiDPI = true) {
if (typeof svgOrSrc == 'string' && hasHiDPI && window.devicePixelRatio >= 1.25) {
@ -59,99 +59,15 @@ function i(name, svgOrSrc, hasHiDPI = true) {
parts[parts.length - 2] = parts[parts.length - 2] + '@2x';
svgOrSrc = parts.join('.');
}
const icon = class extends PureComponent {
render() {
let props = Object.assign({}, this.props);
props.name = name.toLowerCase();
if (typeof svgOrSrc == 'string') {
if (!("style" in props)) props.style = {};
props.style.backgroundImage = `url(${svgOrSrc})`;
props.className = props.className || "";
props.className += " icon-bg";
// We use css background-image.
// This is a performance optimization for fast-scrolling trees.
// If we use img elements they are slow to render
// and produce pop-in when fast-scrolling.
return (
<Icon {...props} />
);
}
return (
<Icon {...props}>{svgOrSrc}</Icon>
)
}
}
icon.propTypes = {
className: string
}
icon.displayName = `Icon${name}`
module.exports[icon.displayName] = icon
legacyIconsCache[`Icon${name}`] = svgOrSrc;
}
/* eslint-disable max-len */
i('Twisty', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13.4c-.5 0-.9-.2-1.2-.6L.4 5.2C0 4.7-.1 4.3.2 3.7S1 3 1.6 3h12.8c.6 0 1.2.1 1.4.7.3.6.2 1.1-.2 1.6l-6.4 7.6c-.3.4-.7.5-1.2.5z"/>
</svg>
));
i('Cross', "chrome://zotero/skin/cross.png");
i('Tick', "chrome://zotero/skin/tick.png");
i('ArrowRefresh', "chrome://zotero/skin/arrow_refresh.png");
if (Zotero.isMac) {
i('Twisty', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<polyline points="3 4 12 4 7.5 12"/>
</svg>
));
}
if (Zotero.isWin) {
i('Twisty', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1792 1792">
<path d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z"/>
</svg>
));
}
let domElementCache = {};
/**
* Returns a DOM element for the icon class
*
* To be used in itemTree where rendering is done without react
* for performance reasons
* @param {String} icon
* @returns {Element}
*/
module.exports.getDOMElement = function (icon) {
if (domElementCache[icon]) return domElementCache[icon].cloneNode(true);
if (!module.exports[icon]) {
Zotero.debug(`Attempting to get non-existant icon ${icon}`);
return "";
}
let div = document.createElement('div');
div.innerHTML = renderToStaticMarkup(React.createElement(module.exports[icon]));
domElementCache[icon] = div.firstChild;
return domElementCache[icon].cloneNode(true);
};
let cssIconsCache = new Map();
@ -161,6 +77,11 @@ module.exports.getCSSIcon = function (key) {
iconEl.classList.add('icon');
iconEl.classList.add('icon-css');
iconEl.classList.add(`icon-${key}`);
// Temporarily set background image for a few remaining png icons
if (legacyIconsCache[key]) {
iconEl.style.backgroundImage = `url(${legacyIconsCache[key]})`;
iconEl.classList.add("icon-bg");
}
cssIconsCache.set(key, iconEl);
}

View file

@ -24,7 +24,7 @@
*/
import React, { memo, useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { getDOMElement } from 'components/icons';
import { getCSSIcon } from 'components/icons';
import VirtualizedTable, { renderCell } from 'components/virtualized-table';
import { nextHTMLID, noop } from './utils';
@ -32,13 +32,13 @@ import { nextHTMLID, noop } from './utils';
function getImageByStatus(status) {
if (status === Zotero.ProgressQueue.ROW_PROCESSING) {
return getDOMElement('IconArrowRefresh');
return getCSSIcon('IconArrowRefresh');
}
else if (status === Zotero.ProgressQueue.ROW_FAILED) {
return getDOMElement('IconCross');
return getCSSIcon('IconCross');
}
else if (status === Zotero.ProgressQueue.ROW_SUCCEEDED) {
return getDOMElement('IconTick');
return getCSSIcon('IconTick');
}
return document.createElement('span');
}

View file

@ -30,7 +30,7 @@ const PropTypes = require('prop-types');
const cx = require('classnames');
const WindowedList = require('./windowed-list');
const Draggable = require('./draggable');
const { CSSIcon, getDOMElement } = require('components/icons');
const { CSSIcon, getCSSIcon } = require('components/icons');
const { Zotero_Tooltip } = require('./tooltip');
const TYPING_TIMEOUT = 1000;
@ -1751,7 +1751,7 @@ function renderCheckboxCell(index, data, column, dir = null) {
span.setAttribute('role', 'checkbox');
span.setAttribute('aria-checked', data);
if (data) {
span.appendChild(getDOMElement('IconTick'));
span.appendChild(getCSSIcon('IconTick'));
}
return span;
}

View file

@ -31,7 +31,7 @@ const LibraryTree = require('./libraryTree');
const VirtualizedTable = require('components/virtualized-table');
const { renderCell, formatColumnName } = VirtualizedTable;
const Icons = require('components/icons');
const { getDOMElement, getCSSIcon, getCSSItemTypeIcon } = Icons;
const { getCSSIcon, getCSSItemTypeIcon } = Icons;
const { COLUMNS } = require("zotero/itemTreeColumns");
const { Cc, Ci, Cu, ChromeUtils } = require('chrome');
const { OS } = ChromeUtils.importESModule("chrome://zotero/content/osfile.mjs");
@ -2796,7 +2796,7 @@ var ItemTree = class ItemTree extends LibraryTree {
let retracted = "";
let retractedAriaLabel = "";
if (Zotero.Retractions.isRetracted(item)) {
retracted = getDOMElement('IconCross');
retracted = getCSSIcon("IconCross");
retracted.classList.add("retracted");
retractedAriaLabel = Zotero.getString('retraction.banner');
}
@ -2916,7 +2916,7 @@ var ItemTree = class ItemTree extends LibraryTree {
}
//else if (type == 'none') {
// if (item.getField('url') || item.getField('DOI')) {
// icon = getDOMElement('IconLink');
// icon = getCSSIcon('IconLink');
// ariaLabel = Zotero.getString('pane.item.attachments.hasLink');
// icon.classList.add('cell-icon');
// }

View file

@ -30,7 +30,6 @@ Components.utils.import("resource://zotero/config.js");
var React = require('react');
var ReactDOM = require('react-dom');
var VirtualizedTable = require('components/virtualized-table');
var { getDOMElement } = require('components/icons');
var { renderCell } = VirtualizedTable;
Zotero_Preferences.Sync = {