From ec625be3b47a9ce1c2d21fe9176e2d418b5b7f0c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Jun 2016 10:28:47 +0530 Subject: [PATCH] Use a single stylesheet instead of per widget instance style sheets Better for DOM mutation performance --- src/pyj/book_list/book_details.pyj | 20 +++++++++----- src/pyj/book_list/item_list.pyj | 24 ++++++++++------- src/pyj/book_list/prefs.pyj | 2 -- src/pyj/book_list/search.pyj | 43 +++++++++++++++++------------- src/pyj/book_list/top_bar.pyj | 43 +++++++++++++++--------------- src/pyj/book_list/views.pyj | 18 +++++++++---- src/pyj/modals.pyj | 23 +++++++++------- src/pyj/read_book/overlay.pyj | 30 ++++++++++++--------- src/pyj/widgets.pyj | 7 +++++ 9 files changed, 124 insertions(+), 86 deletions(-) diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index 11616732f3..dce565d437 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -11,7 +11,7 @@ from elementmaker import E from gettext import gettext as _ from book_list.globals import get_boss from modals import error_dialog -from widgets import create_spinner, create_button +from widgets import create_spinner, create_button, add_extra_css from date import format_date from utils import fmt_sidx @@ -121,6 +121,7 @@ def render_metadata(mi, interface_data, table, field_list=None): def process_formats(field, fm, name, val): table.appendChild(E.tr(E.td(name + ':'), E.td())) + # TODO: Change this to have the read/download options in a popup for fmt in val: td = table.lastChild.lastChild td.appendChild(E.span(fmt, style='white-space: nowrap')) @@ -255,6 +256,16 @@ def render_metadata(mi, interface_data, table, field_list=None): if i is 0: div.style.marginTop = '2ex' +CLASS_NAME = 'book-details-panel' + +add_extra_css(def(): + sel = '.' + CLASS_NAME + ' ' + style = build_rule(sel + 'table.metadata td:first-of-type', font_weight='bold', padding_right='1em', white_space='nowrap') + style += build_rule(sel + 'table.metadata a[href]', color='blue') + style += build_rule(sel + 'table.metadata a[href]:hover', color='red') + style += build_rule(sel + 'table.metadata a[href]:active', color='red', transform='scale(1.5)') + return style +) class BookDetailsPanel: @@ -262,13 +273,8 @@ class BookDetailsPanel: nonlocal bd_counter bd_counter += 1 self.container_id = 'book-details-panel-' + bd_counter - style = build_rule('#' + self.container_id + ' table.metadata td:first-of-type', font_weight='bold', padding_right='1em', white_space='nowrap') - style += build_rule('#' + self.container_id + ' table.metadata a[href]', color='blue') - style += build_rule('#' + self.container_id + ' table.metadata a[href]:hover', color='red') - style += build_rule('#' + self.container_id + ' table.metadata a[href]:active', color='red', transform='scale(1.5)') div = E.div( - id=self.container_id, style='display:none', - E.style(style, type='text/css'), + id=self.container_id, style='display:none', class_=CLASS_NAME, E.div(), ) book_list_container.appendChild(div) diff --git a/src/pyj/book_list/item_list.pyj b/src/pyj/book_list/item_list.pyj index 4b6e6022ba..2c7a0dc99f 100644 --- a/src/pyj/book_list/item_list.pyj +++ b/src/pyj/book_list/item_list.pyj @@ -4,10 +4,23 @@ from __python__ import hash_literals from dom import build_rule, svgicon from elementmaker import E +from widgets import add_extra_css from book_list.theme import get_font_size, get_color iv_counter = 0 +CLASS_NAME = 'generic-items-list' + +add_extra_css(def(): + sel = '.' + CLASS_NAME + ' ' + style = '' + style += build_rule(sel + 'li', padding='1em', border_bottom='solid 1px ' + get_color('window-foreground'), border_top='solid 1px ' + get_color('window-background'), cursor='pointer', list_style='none') + style += build_rule(sel + '.item-title', font_size=get_font_size('item-list-title')) + style += build_rule(sel + ' .item-subtitle', font_size=get_font_size('item-list-subtitle'), font_style='italic') + style += build_rule(sel + ' li:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background'), border_top_color=get_color('list-hover-foreground')) + style += build_rule(sel + ' li:active', transform='scale(1, 1.5)') + return style +) class ItemsView: @@ -15,17 +28,8 @@ class ItemsView: nonlocal iv_counter iv_counter += 1 self.container_id = 'items-view-' + iv_counter - style = '' - cid = '#' + self.container_id - style += build_rule(cid + ' li', padding='1em', border_bottom='solid 1px ' + get_color('window-foreground'), border_top='solid 1px ' + get_color('window-background'), cursor='pointer', list_style='none') - style += build_rule(cid + ' .item-title', font_size=get_font_size('item-list-title')) - style += build_rule(cid + ' .item-subtitle', font_size=get_font_size('item-list-subtitle'), font_style='italic') - style += build_rule(cid + ' li:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background'), border_top_color=get_color('list-hover-foreground')) - style += build_rule(cid + ' li:active', transform='scale(1, 1.5)') - self.base_style = style div = E.div( - id=self.container_id, style='display:none', - E.style(style, type='text/css') + id=self.container_id, style='display:none', class_=CLASS_NAME, ) book_list_container.appendChild(div) diff --git a/src/pyj/book_list/prefs.pyj b/src/pyj/book_list/prefs.pyj index e783b76f23..33f0333afb 100644 --- a/src/pyj/book_list/prefs.pyj +++ b/src/pyj/book_list/prefs.pyj @@ -175,10 +175,8 @@ class PrefsPanel: nonlocal iv_counter pp_counter += 1 self.container_id = 'prefs-panel-' + pp_counter - style = '' div = E.div( id=self.container_id, style='display:none', - E.style(style, type='text/css') ) book_list_container.appendChild(div) self.widgets = [] diff --git a/src/pyj/book_list/search.pyj b/src/pyj/book_list/search.pyj index 6898e735da..91b5a0e3bc 100644 --- a/src/pyj/book_list/search.pyj +++ b/src/pyj/book_list/search.pyj @@ -6,12 +6,33 @@ from ajax import ajax from dom import clear, set_css, build_rule, svgicon from elementmaker import E from gettext import gettext as _ -from widgets import create_button, create_spinner, Breadcrumbs +from widgets import create_button, create_spinner, Breadcrumbs, add_extra_css from modals import show_modal from book_list.globals import get_boss, get_session_data from book_list.theme import get_color, get_font_size sp_counter = 0 +CLASS_NAME = 'book-search-panel' +add_extra_css(def(): + sel = '.' + CLASS_NAME + ' ' + + style = build_rule(sel + ' div.tag-name:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) + style += build_rule(sel + ' div.tag-menu:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) + style += build_rule(sel + ' div.tag-name:active', transform='scale(1.5)') + style += build_rule(sel + ' div.tag-menu:active', transform='scale(2)') + + # search items list + style += build_rule(sel + ' ul.search-items', margin_top='1ex', list_style_type='none', text_align='left') + style += build_rule(sel + ' ul.search-items > li', display='inline-block', cursor='pointer', background_color=get_color('window-background2'), border_radius='10px', padding='0.5ex', margin_right='1em') + style += build_rule(sel + ' ul.search-items > li:hover', color='red') + style += build_rule(sel + ' ul.search-items > li:active', transform='scale(1.5)') + + # Actions popup + style += build_rule('#modal-container ul.tb-action-list > li:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) + style += build_rule('#modal-container ul.tb-action-list > li:active', color='red', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) + + return style +) class SearchPanel: @@ -21,20 +42,9 @@ class SearchPanel: self.container_id = 'search-panel-' + sp_counter self.interface_data = interface_data self.tag_path = [] - style = build_rule('#' + self.container_id + ' div.tag-name:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) - style += build_rule('#' + self.container_id + ' div.tag-menu:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) - style += build_rule('#' + self.container_id + ' div.tag-name:active', transform='scale(1.5)') - style += build_rule('#' + self.container_id + ' div.tag-menu:active', transform='scale(2)') - - # search items list - style += build_rule('#' + self.container_id + ' ul.search-items', margin_top='1ex', list_style_type='none', text_align='left') - style += build_rule('#' + self.container_id + ' ul.search-items > li', display='inline-block', cursor='pointer', background_color=get_color('window-background2'), border_radius='10px', padding='0.5ex', margin_right='1em') - style += build_rule('#' + self.container_id + ' ul.search-items > li:hover', color='red') - style += build_rule('#' + self.container_id + ' ul.search-items > li:active', transform='scale(1.5)') div = E.div( - id=self.container_id, style='display:none', - E.style(style, type='text/css'), + id=self.container_id, style='display:none', class_=CLASS_NAME, E.div(style="text-align:center; padding:1ex 1em; border-bottom: solid 1px currentColor; margin-bottom: 0.5ex"), # search input container E.div( E.div(), @@ -45,7 +55,7 @@ class SearchPanel: book_list_container.appendChild(div) # Build search input - search_container = div.firstChild.nextSibling + search_container = div.firstChild search_button = create_button(_('Search'), icon='search', action=self.execute_search.bind(self), tooltip=_('Do the search')) search_container.appendChild(E.div(style="display: flex; width: 100%;", E.input( @@ -304,11 +314,8 @@ class SearchPanel: if items.length: suffix = ' [' + items.join(' ') + ']' - style = build_rule('#modal-container ul.tb-action-list > li:hover', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) - style += build_rule('#modal-container ul.tb-action-list > li:active', color='red', color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background')) title = E.h2( style='display:flex; align-items: center; border-bottom: solid 1px currentColor; font-weight:bold; font-size:' + get_font_size('title'), - E.style(style), E.img(src=self.icon_for_node(node), style='height:2ex'), E.span('\xa0' + name + suffix) ) @@ -465,7 +472,7 @@ class SearchPanel: @property def search_items_container(self): - return self.container.firstChild.nextSibling.lastChild + return self.container.firstChild.lastChild @property def is_visible(self): diff --git a/src/pyj/book_list/top_bar.pyj b/src/pyj/book_list/top_bar.pyj index 16436330f8..42c1a1419b 100644 --- a/src/pyj/book_list/top_bar.pyj +++ b/src/pyj/book_list/top_bar.pyj @@ -6,41 +6,42 @@ from book_list.theme import get_color, get_font_size from dom import set_css, clear, create_keyframes, build_rule, svgicon from elementmaker import E from gettext import gettext as _ +from widgets import add_extra_css bar_counter = 0 +CLASS_NAME = 'main-top-bar' +SPACING = '0.75em' +VSPACING = '0.5ex' +THROBBER_NAME = 'top-bar-throbber' + +add_extra_css(def(): + sel = '.' + CLASS_NAME + ' ' + style = '' + style += create_keyframes(THROBBER_NAME, 'from { transform: scale(1); } 50% { transform: scale(0.5); } to { transform: scale(1); }') + style += build_rule(sel + 'a', display='inline-block', vertical_align='middle', overflow='hidden', cursor='pointer', color=get_color('bar-foreground'), background='none', padding_top=VSPACING, padding_bottom=VSPACING) + style += build_rule(sel + 'a:hover', transform='scale(1.5)') + style += build_rule(sel + 'a:active', transform='scale(2)') + style += build_rule(sel + 'a:focus', outline='none') + style += build_rule(sel + 'a.top-bar-title:hover', transform='scale(1)', color=get_color('bar-highlight'), font_style='italic') + style += build_rule(sel + 'a.top-bar-title:active', transform='scale(1)', color=get_color('bar-highlight'), font_style='italic') + return style +) class TopBar: - SPACING = '0.75em' - VSPACING = '0.5ex' - def __init__(self, book_list_container): nonlocal bar_counter bar_counter += 1 self.current_left_data = {} self.bar_id, self.dummy_bar_id = 'top-bar-' + bar_counter, 'dummy-top-bar-' + bar_counter - self.throbber_name = self.bar_id + '-throbber' - style = create_keyframes(self.throbber_name, 'from { transform: scale(1); } 50% { transform: scale(0.5); } to { transform: scale(1); }') - for sel in (self.dummy_bar_id, self.bar_id): - sel = '#' + sel + ' a' - style += build_rule( - sel, display='inline-block', vertical_align='middle', overflow='hidden', cursor='pointer', - color=get_color('bar-foreground'), background='none', padding_top=self.VSPACING, padding_bottom=self.VSPACING - ) - style += build_rule(sel + ':hover', transform='scale(1.5)') - style += build_rule(sel + ':active', transform='scale(2)') - style += build_rule(sel + ':focus', outline='none') - style += build_rule(sel + '.top-bar-title:hover', transform='scale(1)', color=get_color('bar-highlight'), font_style='italic') - style += build_rule(sel + '.top-bar-title:active', transform='scale(1)', color=get_color('bar-highlight'), font_style='italic') for bid in self.dummy_bar_id, self.bar_id: bar = E.div( - id=bid, + id=bid, class_=CLASS_NAME, E.div(style="white-space:nowrap; overflow:hidden; text-overflow: ellipsis; padding-left: 0.5em;"), E.div(style="white-space:nowrap; text-align:right; padding-right: 0.5em;") ) if bid is self.bar_id: set_css(bar, position='fixed', left='0', top='0', z_index='1') - bar.appendChild(E.style(style, type='text/css')) set_css(bar, width='100%', display='flex', flex_direction='row', flex_wrap='wrap', justify_content='space-between', font_size=get_font_size('title'), user_select='none', @@ -73,12 +74,12 @@ class TopBar: title_elem = 'a' if callable(title_action) else 'span' left.appendChild(E.a(title=tooltip, svgicon(icon_name))) left.appendChild(E(title_elem, title, title=title_tooltip, class_='top-bar-title', - style='margin-left: {0}; font-weight: bold; padding-top: {1}; padding-bottom: {1}; vertical-align: middle'.format(self.SPACING, self.VSPACING))) + style='margin-left: {0}; font-weight: bold; padding-top: {1}; padding-bottom: {1}; vertical-align: middle'.format(SPACING, VSPACING))) if bar is self.bar: a = left.firstChild if icon_name is 'heart': set_css(a, - animation_name=self.throbber_name, animation_duration='1s', animation_timing_function='ease-in-out', + animation_name=THROBBER_NAME, animation_duration='1s', animation_timing_function='ease-in-out', animation_iteration_count='5', animation_play_state='running' if run_animation else 'paused' ) set_css(a.firstChild, color=get_color('heart')) @@ -101,7 +102,7 @@ class TopBar: for bar in self.bar, self.dummy_bar: right = bar.firstChild.nextSibling right.appendChild(E.a( - style="margin-left: " + self.SPACING, + style="margin-left: " + SPACING, title=tooltip, svgicon(icon_name), )) right.lastChild.setAttribute('id', ('top' if bar is self.bar else 'dummy') + '-bar-icon-' + icon_name) diff --git a/src/pyj/book_list/views.pyj b/src/pyj/book_list/views.pyj index 2c1538584f..afd8c784e9 100644 --- a/src/pyj/book_list/views.pyj +++ b/src/pyj/book_list/views.pyj @@ -4,10 +4,11 @@ from __python__ import hash_literals import traceback from ajax import ajax_send -from dom import set_css, build_rule, clear +from dom import set_css, clear, build_rule from elementmaker import E from gettext import gettext as _ from modals import error_dialog, ajax_progress_dialog +from widgets import add_extra_css from book_list.globals import get_session_data, get_boss from widgets import create_button, create_spinner @@ -17,6 +18,16 @@ THUMBNAIL_MAX_HEIGHT = 400 bv_counter = 0 +CLASS_NAME = 'books-main-list' + +def widget_css(): + ans = '' + sel = '.' + CLASS_NAME + ' ' + ans += build_rule(sel + '.cover_grid > div:hover', transform='scale(1.2)') + ans += build_rule(sel + '.cover_grid > div:active', transform='scale(2.0)') + return ans +add_extra_css(widget_css) + class BooksView: def __init__(self, interface_data, book_list_container): @@ -29,11 +40,8 @@ class BooksView: # We have to apply the transform on the containing div not the img because of a bug in WebKit # that causes img aspect ratios to be messed up on window resize if the transform is specified # on the img itself - style = build_rule('#' + self.container_id + ' .cover_grid > div:hover', transform='scale(1.2)') - style += build_rule('#' + self.container_id + ' .cover_grid > div:active', transform='scale(2.0)') div = E.div( - id=self.container_id, style='display:block', - E.style(style), + id=self.container_id, style='display:block', class_=CLASS_NAME, E.div(), E.div() ) diff --git a/src/pyj/modals.pyj b/src/pyj/modals.pyj index 41017b0b1b..8bbeabeb96 100644 --- a/src/pyj/modals.pyj +++ b/src/pyj/modals.pyj @@ -8,10 +8,23 @@ from dom import set_css, clear, build_rule, svgicon from gettext import gettext as _ from book_list.theme import get_color, get_font_size from popups import MODAL_Z_INDEX +from widgets import add_extra_css modal_container = None modal_count = 0 +add_extra_css(def(): + style = build_rule( + '#modal-container > div > a:hover', + color=get_color('dialog-foreground') + ' !important', + background_color=get_color('dialog-background') + ' !important' + ) + style += build_rule( + '#modal-container a.dialog-simple-link:hover', color='red !important' + ) + return style +) + class Modal: def __init__(self, create_func, on_close, show_close): @@ -27,16 +40,6 @@ class ModalContainer: E.div( # popup E.div(), # content area E.a(svgicon('close'), title=_('Close')) - ), - E.style( - build_rule( - '#modal-container > div > a:hover', - color=get_color('dialog-foreground') + ' !important', - background_color=get_color('dialog-background') + ' !important' - ) + - build_rule( - '#modal-container a.dialog-simple-link:hover', color='red !important' - ) ) ) document.body.appendChild(div) diff --git a/src/pyj/read_book/overlay.pyj b/src/pyj/read_book/overlay.pyj index bbc72046ba..6e37d508a9 100644 --- a/src/pyj/read_book/overlay.pyj +++ b/src/pyj/read_book/overlay.pyj @@ -6,7 +6,7 @@ from dom import clear, set_css, element, svgicon, build_rule from elementmaker import E from book_list.theme import get_color from book_list.globals import get_boss -from widgets import create_spinner, create_button +from widgets import create_spinner, create_button, add_extra_css from gettext import gettext as _ from read_book.toc import create_toc_panel @@ -93,7 +93,21 @@ class DeleteBook: # {{{ self.overlay.hide_current_panel() # }}} -class MainOverlay: # {{{ +# MainOverlay {{{ + +MAIN_OVERLAY_TS_CLASS = 'read-book-main-overlay-top-section' + +add_extra_css(def(): + sel = '.' + MAIN_OVERLAY_TS_CLASS + ' ' + style = build_rule(sel + '.button-row > div:hover', transform='scale(1.5)') + style += build_rule(sel + '.button-row > div:active', transform='scale(2)') + style += build_rule(sel + '.item-list > li', padding='1ex 1rem', border_bottom='solid 1px currentColor', cursor='pointer') + style += build_rule(sel + '.item-list > li:hover', color=get_color('window-background'), background_color=get_color('window-foreground')) + style += build_rule(sel + '.item-list > li:active', transform='scaleY(2)') + return style +) + +class MainOverlay: def __init__(self, overlay): self.overlay = overlay @@ -107,7 +121,7 @@ class MainOverlay: # {{{ def show(self, container): self.container_id = container.getAttribute('id') - container.appendChild(set_css(E.div( # top section + container.appendChild(set_css(E.div(class_=MAIN_OVERLAY_TS_CLASS, # top section onclick=def (evt):evt.stopPropagation();, set_css(E.div( # top row @@ -157,16 +171,6 @@ class MainOverlay: # {{{ add_button('cogs', _('Configure the book reader')) add_button() - sel = '#{} .button-row '.format(self.container_id) - sel2 = '#{} .item-list'.format(self.container_id) - container.appendChild(E.style( - build_rule(sel + '> div:hover', transform='scale(1.5)'), - build_rule(sel + '> div:active', transform='scale(2)'), - build_rule(sel2 + '> li', padding='1ex 1rem', border_bottom='solid 1px currentColor', cursor='pointer'), - build_rule(sel2 + '> li:hover', color=get_color('window-background'), background_color=get_color('window-foreground')), - build_rule(sel2 + '> li:active', transform='scaleY(2)'), - )) - def update_time(self): element(self.container_id, '[data-time]').textContent = self.date_formatter.format(Date()) diff --git a/src/pyj/widgets.pyj b/src/pyj/widgets.pyj index a6481e7e89..86871e3e5e 100644 --- a/src/pyj/widgets.pyj +++ b/src/pyj/widgets.pyj @@ -202,10 +202,17 @@ def scroll_tree_item_into_view(item): # }}} +extra_css = [] + +def add_extra_css(func): + extra_css.push(func) + def get_widget_css(): ans = 'a, button:focus { outline: none }; a, button::-moz-focus-inner { border: 0 }\n' ans += '.simple-link { cursor: pointer } .simple-link:hover { color: red } .simple-link:active { transform: scale(1.5) }\n' ans += create_button.style ans += create_spinner.style ans += Breadcrumbs.STYLE_RULES + for func in extra_css: + ans += '\n' + func() return ans