diff --git a/theme_prime/CHANGELOG.md b/theme_prime/CHANGELOG.md new file mode 100644 index 000000000..cb02226ec --- /dev/null +++ b/theme_prime/CHANGELOG.md @@ -0,0 +1,11 @@ +## [17.0.0.0.4] +# Ui improvements for domain builder +## [17.0.0.0.3] +# Usability improvement for tab snippet +## [17.0.0.0.2] +# Technical improvements for buider tools +## [17.0.0.0.1] +# Fix error when there is no pricelist and user do add to cart. +## [17.0.0.0.0] +### Added +- Initial release. diff --git a/theme_prime/COPYRIGHT b/theme_prime/COPYRIGHT new file mode 100644 index 000000000..e27598c6a --- /dev/null +++ b/theme_prime/COPYRIGHT @@ -0,0 +1,16 @@ + +Most of the files are + +Copyright (c) 2019 - present Droggol Infotech Private Limited. + +Some javascript files might be from from third +parties libraries. In that case the original +copyright of the contributions can be traced +through the history of the source version +control system. + +When that is not the case, the files contain a prominent +notice stating the original copyright and applicable +license, or come with their own dedicated COPYRIGHT +and/or LICENSE file. + diff --git a/theme_prime/LICENSE b/theme_prime/LICENSE new file mode 100644 index 000000000..7a5c68e71 --- /dev/null +++ b/theme_prime/LICENSE @@ -0,0 +1,27 @@ +Odoo Proprietary License v1.0 + +This software and associated files (the "Software") may only be used (executed, +modified, executed after modifications) if you have purchased a valid license +from the authors, typically via Odoo Apps, or if you have received a written +agreement from the authors of the Software (see the COPYRIGHT file). + +You may develop Odoo modules that use the Software as a library (typically +by depending on it, importing it and using its resources), but without copying +any source code or material from the Software. You may distribute those +modules under the license of your choice, provided that this license is +compatible with the terms of the Odoo Proprietary License (For example: +LGPL, MIT, or proprietary licenses similar to this one). + +It is forbidden to publish, distribute, sublicense, or sell copies of the Software +or modified copies of the Software. + +The above copyright notice and this permission notice must be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/theme_prime/__init__.py b/theme_prime/__init__.py new file mode 100644 index 000000000..3a586451a --- /dev/null +++ b/theme_prime/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +from . import controllers +from . import models diff --git a/theme_prime/__manifest__.py b/theme_prime/__manifest__.py new file mode 100644 index 000000000..fa68b62d7 --- /dev/null +++ b/theme_prime/__manifest__.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +{ + 'name': 'Theme Prime', + 'description': 'Powerful multipurpose eCommerce theme suitable for all kind of businesses like Electronics, Fashion, Sports, Beauty, Furniture and many more.', + 'summary': 'Powerful multipurpose eCommerce theme suitable for all kind of businesses like Electronics, Fashion, Sports, Beauty, Furniture and many more.', + 'category': 'Theme/eCommerce', + 'version': '17.0.0.4', + 'depends': ['droggol_theme_common'], + + 'license': 'OPL-1', + 'author': 'Droggol Infotech Private Limited', + 'company': 'Droggol Infotech Private Limited', + 'maintainer': 'Droggol Infotech Private Limited', + 'website': 'https://www.droggol.com/', + + 'price': 289.00, + 'currency': 'USD', + 'live_test_url': 'https://prime-17-electronics-1.droggol.com/', + + 'images': [ + 'static/description/prime_cover.png', + 'static/description/prime_screenshot.gif', + ], + 'data': [ + 'data/theme.ir.attachment.csv', + + 'views/sidebar.xml', + 'views/templates.xml', + 'views/components.xml', + 'views/layout.xml', + 'views/shop_layout.xml', + 'views/product_detail_page.xml', + 'views/pages.xml', + 'views/snippets.xml', + 'views/svg_images.xml', + + # Headers / Footers + 'views/headers.xml', + 'views/preheaders.xml', + 'views/footers.xml', + + # Snippets + 'views/snippets/dynamic_snippets.xml', + 'views/snippets/s_banner.xml', + 'views/snippets/s_blog.xml', + 'views/snippets/s_clients.xml', + 'views/snippets/s_coming_soon.xml', + 'views/snippets/s_countdown.xml', + 'views/snippets/s_cover.xml', + 'views/snippets/s_cta.xml', + 'views/snippets/s_gallery.xml', + 'views/snippets/s_heading.xml', + 'views/snippets/s_icon_block.xml', + 'views/snippets/s_info_block.xml', + 'views/snippets/s_pricing.xml', + 'views/snippets/s_shop_offer.xml', + 'views/snippets/s_stats.xml', + 'views/snippets/s_subscribe.xml', + 'views/snippets/s_team.xml', + 'views/snippets/s_testimonial.xml', + ], + 'assets': { + 'web.assets_frontend': [ + ('prepend', 'theme_prime/static/src/js/website_sale_utils.js'), + # Libraries + 'theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.carousel.css', + 'theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.theme.default.css', + # Frontend + 'theme_prime/static/src/js/website.js', + 'theme_prime/static/src/js/website_sale.js', + 'theme_prime/static/src/js/website_sale_wishlist.js', + + 'theme_prime/static/src/js/sidebar.js', + 'theme_prime/static/src/xml/sidebar.xml', + + 'theme_prime/static/src/js/suggested_product_slider.js', + 'theme_prime/static/src/xml/frontend/suggested_product_slider.xml', + + 'theme_prime/static/src/js/service_worker_register.js', + 'theme_prime/static/src/xml/pwa.xml', + + 'theme_prime/static/src/js/core/mixins.js', + 'theme_prime/static/src/js/frontend/comparison.js', + 'theme_prime/static/src/js/frontend/quick_view_dialog.js', + 'theme_prime/static/src/js/frontend/bottombar.js', + + 'theme_prime/static/src/scss/theme.scss', + 'theme_prime/static/src/scss/rtl.scss', + 'theme_prime/static/src/scss/variants.scss', + 'theme_prime/static/src/scss/website.scss', + 'theme_prime/static/src/scss/website_sale.scss', + 'theme_prime/static/src/scss/sliders.scss', + 'theme_prime/static/src/scss/icon-packs/website.scss', + 'theme_prime/static/src/scss/utils.scss', + 'theme_prime/static/src/scss/snippets/cards.scss', + 'theme_prime/static/src/scss/front_end/quick_view.scss', + 'theme_prime/static/src/scss/front_end/dynamic_snippets.scss', + 'theme_prime/static/src/scss/front_end/category_filters.scss', + 'theme_prime/static/src/scss/front_end/image_hotspot.scss', + 'theme_prime/static/src/scss/snippets/2_col_deal.scss', + 'theme_prime/static/src/scss/snippets/image_products.scss', + 'theme_prime/static/src/scss/front_end/bottom_bar.scss', + 'theme_prime/static/src/snippets/s_blog_posts/000.scss', + # Core + 'theme_prime/static/src/js/core/snippet_root_widget.js', + 'theme_prime/static/src/xml/core/snippet_root_widget.xml', + + 'theme_prime/static/src/js/core/product_root_widget.js', + + 'theme_prime/static/src/js/core/cart_manager.js', + 'theme_prime/static/src/xml/frontend/notification_template.xml', + + 'theme_prime/static/src/js/core/cart_confirmation_dialog.js', + 'theme_prime/static/src/xml/core/cart_confirmation_dialog.xml', + + # Snippets + 'theme_prime/static/src/snippets/s_tp_countdown/000.xml', + 'theme_prime/static/src/js/frontend/dynamic_snippets.js', + 'theme_prime/static/src/xml/frontend/dynamic_snippets.xml', + 'theme_prime/static/src/xml/cards.xml', + 'theme_prime/static/src/xml/listing_cards.xml', + 'theme_prime/static/src/xml/frontend/utils.xml', + 'theme_prime/static/src/xml/frontend/category_filters.xml', + 'theme_prime/static/src/xml/frontend/2_col_deal.xml', + 'theme_prime/static/src/xml/frontend/s_image_products.xml', + 'theme_prime/static/src/xml/frontend/s_product_grid.xml', + 'theme_prime/static/src/xml/frontend/hierarchical_category_templates.xml', + 'theme_prime/static/src/xml/frontend/s_category.xml', + 'theme_prime/static/src/xml/frontend/brands.xml', + 'theme_prime/static/src/xml/frontend/image_hotspot.xml', # TODO: kishan + + # Editor + ('replace', 'web_editor/static/src/scss/web_editor.frontend.scss', 'theme_prime/static/src/scss/web_editor.frontend.scss'), + + # Search + 'theme_prime/static/src/js/frontend/search.js', + 'theme_prime/static/src/xml/frontend/search_autocomplete.xml', + ], + 'web._assets_primary_variables': [ + 'theme_prime/static/src/scss/primary_variables.scss', + 'theme_prime/static/src/scss/mixins.scss', + ], + 'web._assets_frontend_helpers': [ + 'theme_prime/static/src/scss/bootstrap_overridden.scss', + ], + 'website.assets_wysiwyg': [ + 'droggol_theme_common/static/src/js/hooks.js', + 'theme_prime/static/src/js/editor/snippets.editor.js', + 'theme_prime/static/src/scss/editor/editor.scss', + + # 'theme_prime/static/src/scss/editor/dialogs/dialog_snippet_configurator.scss', + 'theme_prime/static/src/xml/editor/dialogs/snippet_configurator_dialog.xml', + 'theme_prime/static/src/xml/frontend/image_hotspot.xml', + + 'theme_prime/static/src/js/editor/snippets/snippets.options.js', + 'theme_prime/static/src/xml/frontend/documents.xml', + 'theme_prime/static/src/components/*' + ], + + }, +} diff --git a/theme_prime/controllers/__init__.py b/theme_prime/controllers/__init__.py new file mode 100644 index 000000000..ef9adf569 --- /dev/null +++ b/theme_prime/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +from . import main diff --git a/theme_prime/controllers/main.py b/theme_prime/controllers/main.py new file mode 100644 index 000000000..694e8cdfc --- /dev/null +++ b/theme_prime/controllers/main.py @@ -0,0 +1,1051 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +import base64 +import io +import json +import re +import string +from collections import defaultdict +import datetime + +try: + from werkzeug.utils import send_file +except ImportError: + from odoo.tools._vendor.send_file import send_file + +from odoo import _, http +from odoo.addons.website.controllers.main import Website +from odoo.addons.website_sale.controllers.main import WebsiteSale +from odoo.addons.website_sale_wishlist.controllers.main import WebsiteSaleWishlist +from odoo.http import request +from odoo.osv import expression +from odoo.tools import html_escape, file_path, file_open +from odoo.tools.mimetypes import guess_mimetype + + +class ThemePrimeWebsiteSale(WebsiteSale): + + def _get_search_domain(self, search, category, attrib_values, search_in_description=True, search_rating=True): + """ Overrided method used to apply extra filters. + + Extra parameters are added to skip some filters (Used for attribute count) + :param search_rating: if false rating domain will not be added + """ + domains = super()._get_search_domain(search, category, attrib_values, search_in_description) + + # Hide out of stock + if request.httprequest.args.get('hide_out_of_stock'): + domains = expression.AND([domains, ['|', '|', ('detailed_type', '!=', 'product'), ('allow_out_of_stock_order', '=', True), '&', ('dr_free_qty', '>', 0), ('allow_out_of_stock_order', '=', False)]]) + + # Rating + ratings = request.httprequest.args.getlist('rating') + if ratings and search_rating: + result = request.env['rating.rating'].sudo().read_group([('res_model', '=', 'product.template')], ['rating:avg'], groupby=['res_id'], lazy=False) + rating_product_ids = [] + for rating in ratings: + rating_product_ids.extend([item['res_id'] for item in result if item['rating'] >= int(rating)]) + if rating_product_ids: + domains = expression.AND([domains, [('id', 'in', rating_product_ids)]]) + else: + domains = expression.AND([domains, [('id', 'in', [])]]) + return domains + + def _get_search_options(self, category=None, attrib_values=None, tags=None, pricelist=None, min_price=0.0, max_price=0.0, conversion_rate=1, **post): + res = super()._get_search_options(category=category, attrib_values=attrib_values, tags=tags, pricelist=pricelist, min_price=min_price, max_price=max_price, conversion_rate=conversion_rate, **post) + if request.httprequest.args.get('hide_out_of_stock'): + res['hide_out_of_stock'] = request.httprequest.args.get('hide_out_of_stock') + if post.get('rating') != 'skip' and request.httprequest.args.getlist('rating'): + res['rating'] = request.httprequest.args.getlist('rating') + return res + + def _shop_get_query_url_kwargs(self, category, search, min_price, max_price, attrib=None, order=None, tags=None, **post): + result = super()._shop_get_query_url_kwargs(category, search, min_price, max_price, attrib=attrib, order=order, tags=tags, **post) + result.update( + view_mode=post.get('view_mode'), + hide_out_of_stock=post.get('hide_out_of_stock'), + rating=post.get('rating'), + ) + return result + + @http.route() + def shop(self, page=0, category=None, search='', min_price=0.0, max_price=0.0, ppg=False, **post): + response = super().shop(page=page, category=category, search=search, min_price=min_price, max_price=max_price, ppg=ppg, **post) + + try: + min_price = float(min_price) + except ValueError: + min_price = 0 + try: + max_price = float(max_price) + except ValueError: + max_price = 0 + theme_id = request.website.sudo().theme_id + if theme_id and theme_id.name.startswith('theme_prime'): + + + ProductTemplate = request.env['product.template'] + request_args = request.httprequest.args + + attrib_list = request_args.getlist('attrib') + attrib_values = [[int(x) for x in v.split('-')] for v in attrib_list if v] + attributes_ids = [v[0] for v in attrib_values] + post['tags'] = response.qcontext.get('tags') + + fuzzy_search_term = response.qcontext.get('search') or search + pricelist, category = response.qcontext.get('pricelist'), response.qcontext.get('category') + if request.website._get_dr_theme_config('json_shop_filters')['show_category_count']: + # Categories + domain = self._prepare_filters_domain(search=fuzzy_search_term, attrib_values=attrib_values, pricelist=pricelist, min_price=min_price, max_price=max_price, **post) + response.qcontext.update(get_category_count=ProductTemplate._get_product_category_count(domain=domain)) + if request.website._get_dr_theme_config('json_shop_filters')['show_attribute_count'] or request.website._get_dr_theme_config('json_shop_filters')['hide_extra_attrib_value']: + # Attributes + domain = self._prepare_filters_domain(search=fuzzy_search_term, category=category, pricelist=pricelist, min_price=min_price, max_price=max_price, **post) + response.qcontext.update(get_attrib_count=ProductTemplate._get_product_attrib_count(attrib_values, domain=domain)) + + # Rating + domain = self._prepare_filters_domain(search=fuzzy_search_term, attrib_values=attrib_values, category=category, pricelist=pricelist, min_price=min_price, max_price=max_price, **{**post, 'rating': 'skip'}) + response.qcontext.update(get_ratings_count=ProductTemplate._get_product_rating_count(domain=domain)) + + selected_ratings = [int(x) for x in request_args.getlist('rating')] + selected_hide_out_of_stock = request_args.get('hide_out_of_stock') + + _config_shop_layout = request.website._get_dr_theme_config('json_shop_layout') + if _config_shop_layout.get('layout') == 'prime': + response.template = 'theme_prime.shop_layout' + + response.qcontext.update( + _config_shop_layout=_config_shop_layout, + _config_product_item=request.website._get_dr_theme_config('json_shop_product_item'), + _config_shop_filters=request.website._get_dr_theme_config('json_shop_filters'), + _config_category_pills=request.website._get_dr_theme_config('json_shop_category_pills'), + view_mode=request_args.get('view_mode', _config_shop_layout.get('default_view_mode')), + page=page, + attributes_ids=attributes_ids, + selected_ratings=selected_ratings, + selected_hide_out_of_stock=selected_hide_out_of_stock, + selected_attributes=response.qcontext.get('attrib_set') or request_args.get('min_price') or request_args.get('max_price') or response.qcontext.get('tags') or selected_ratings or selected_hide_out_of_stock + ) + return response + + def _prepare_filters_domain(self, search=None, pricelist=None, category=None, attrib_values=None, min_price=None, max_price=None, **post): + options = self._get_search_options(category=category, attrib_values=attrib_values, pricelist=pricelist, min_price=min_price, max_price=max_price, **post) + search_detail = request.website._search_get_details('products_only', None, options)[0] + search_fields = search_detail['search_fields'] + base_domain = search_detail['base_domain'] + return request.env['product.template']._search_build_domain(base_domain, search, search_fields, search_detail.get('search_extra')) + + def _prepare_product_values(self, product, category, search, **kwargs): + res = super()._prepare_product_values(product, category, search, **kwargs) + if request.website._get_dr_theme_config('bool_show_products_nav'): + ProductTemplate = request.env['product.template'] + res['prev_product_id'] = ProductTemplate.search([('website_sequence', '<', product.website_sequence)] + request.website.website_domain(), limit=1, order='website_sequence desc') + res['next_product_id'] = ProductTemplate.search([('website_sequence', '>', product.website_sequence)] + request.website.website_domain(), limit=1, order='website_sequence') + return res + + @http.route() + def cart(self, access_token=None, revive='', **post): + res = super().cart(access_token=access_token, revive=revive, **post) + if post.get('type') == 'tp_cart_sidebar_request': + order = request.website.sale_get_order() + if order and order.state != 'draft': + request.session['sale_order_id'] = None + return request.render('theme_prime.cart_sidebar', {'order': order}, headers={'Cache-Control': 'no-cache'}) + return res + + @http.route('/theme_prime/get_search_sidebar', type='http', auth='public', website=True, sitemap=False) + def search_sidebar(self, **post): + return request.render('theme_prime.search_sidebar') + + @http.route('/theme_prime/get_categories_list', type='json', auth='public', website=True, sitemap=False) + def _get_categories_list(self, **post): + return { + 'categories': request.env['product.public.category'].search_read(request.website.website_domain(), ['name', 'parent_id', 'child_id']), + 'category_count': request.env['product.template']._get_product_category_count(domain=request.website.website_domain()) if request.website._get_dr_theme_config('json_sidebar_config').get('category_sidebar_show_count') else {}, + } + + @http.route('/theme_prime/get_quick_view_html', type='json', auth='public', website=True) + def get_quick_view_html(self, options, **kwargs): + IrUiView = request.env['ir.ui.view'] + productID = options.get('productID') + variantID = options.get('variantID') + if variantID: + productID = request.env['product.product'].browse(variantID).product_tmpl_id.id + domain = expression.AND([request.website.sale_product_domain(), [('id', '=', productID)]]) + product = request.env['product.template'].search(domain, limit=1) + + # If moved to another website or delete + if not product: + return False + + values = self._prepare_product_values(product, category='', search='', **kwargs) + result = request.website.get_theme_prime_shop_config() + values.update(result) + values['d_url_root'] = request.httprequest.url_root[:-1] + + if options.get('variant_selector'): + values['auto_add_product'] = product.product_variant_count == 1 + return IrUiView._render_template('theme_prime.product_variant_selector_dialog', values=values) + + if options.get('right_panel'): + return IrUiView._render_template('theme_prime.tp_product_right_panel', values=values) + return IrUiView._render_template('theme_prime.tp_product_quick_view', values=values) + + @http.route() + def cart_update_json( + self, product_id, line_id=None, add_qty=None, set_qty=None, display=True, + product_custom_attribute_values=None, no_variant_attribute_values=None, **kw + ): + response = super().cart_update_json( + product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty, display=display, + product_custom_attribute_values=product_custom_attribute_values, no_variant_attribute_values=no_variant_attribute_values, **kw) + + if kw.get('dr_cart_flow') and response: + sale_order = request.website.sale_get_order(force_create=True) + product = request.env['product.product'].browse(int(product_id)) + response.update({ + 'cart_quantity': sale_order.cart_quantity, + 'product_name': product.name, + 'product_id': int(product_id), + 'accessory_product_ids': product.accessory_product_ids and product.accessory_product_ids.mapped('product_tmpl_id').ids or [] + }) + + return response + + @http.route() + def clear_cart(self): + # JAT: Return formatted order amount on clear cart(in sidebar) to properly update cart total in headers. + super().clear_cart() + order = request.website.sale_get_order() + return { + 'notification_info': { + 'order_amount_html': request.env['ir.qweb.field.monetary'].value_to_html(order.amount_total, {'display_currency': order.pricelist_id.currency_id or request.website.company_id.currency_id}) + } + } + + def _get_cart_notification_information(self, order, line_ids): + values = super()._get_cart_notification_information(order, line_ids) + values['order_amount_html'] = request.env['ir.qweb.field.monetary'].value_to_html(order.amount_total, {'display_currency': order.pricelist_id.currency_id or request.website.company_id.currency_id}) + return values + +class DroggolWishlist(WebsiteSaleWishlist): + @http.route('/theme_prime/wishlist_general', auth="public", type='json', website=True) + def wishlist_general(self, product_id=False, **post): + res = {} + if product_id: + res['wishlist_id'] = self.add_to_wishlist(product_id).id + res.update({ + 'products': request.env['product.wishlist'].with_context(display_default_code=False).current().mapped('product_id').ids, + 'name': request.env['product.product'].browse(product_id).name + }) + return res + + +class ThemePrimeMainClass(http.Controller): + + # ---------------------------------------------------------- + # Helper methods + # ---------------------------------------------------------- + # This method is needs to be clean in next version we will do refactor + # Currently our rendering process is weird. + def _get_products(self, domain=None, fields=[], limit=25, order=None, options={}): + pricelist = request.website.pricelist_id + website_sale_domain = request.website.sale_product_domain() + final_domain = expression.AND([website_sale_domain, domain]) + + product_tmpl_ids = [] + temp_order = order + if order == 'last_viewed': + product_tmpl_ids = self._get_last_viewed_products(domain, limit) + final_domain = [('id', 'in', product_tmpl_ids)] + order = None + + # bestseller is not a real field + bestseller_ids, old_limit = [], limit + if order == 'bestseller': + bestseller_ids, limit, order = self._get_bestseller_products(old_limit) + + products = request.env['product.template'].with_context(pricelist=pricelist.id).search(final_domain, limit=limit, order=order) + + if temp_order == 'last_viewed' and len(product_tmpl_ids): + products = products.sorted(lambda p: product_tmpl_ids.index(p.id)) + default_fields = ['id', 'name', 'website_url', 'default_code'] + fields = set(default_fields + fields) + + if bestseller_ids: + bestseller_ids_filtered = set(bestseller_ids) & set(products.ids) + bestseller_sorted_ids = [p_id for p_id in bestseller_ids if p_id in bestseller_ids_filtered] + remain_products = set(products.ids) - set(bestseller_sorted_ids) + final_product_ids = bestseller_sorted_ids + list(remain_products) + products = request.env['product.template'].with_context(pricelist=pricelist.id).browse(final_product_ids[:old_limit]) + + return self._prepare_product_data(products, fields) + + def _get_last_viewed_products(self, domain, limit): + domain = expression.AND([[('website_published', '=', True)], request.website.get_current_website().website_domain(), domain]) + products = request.env['website.snippet.filter']._get_products_latest_viewed(request.website, limit, domain, request.env.context) + return products.mapped("product_tmpl_id").ids if len(products) else [] + + def _prepare_product_data(self, products, fields, options=None): + + options = options or {} + pricelist = request.website.pricelist_id + price_public_visibility = request.website._dr_has_b2b_access() + visibility_label = False + showStockLabel = False + + if not price_public_visibility: + visibility_label = self._get_tp_view_template('theme_prime.tp_b2b_price_label') + + extra_data = {'rating', 'offer_data', 'dr_stock_label', 'colors'} & set(fields) + fields = list(set(fields) - extra_data) + + if 'dr_stock_label' in extra_data: + showStockLabel = request.website._get_dr_theme_config('json_shop_product_item').get('show_stock_label') + currency_id = pricelist.currency_id or request.website.company_id.currency_id + + result = products.read(fields) + + for res_product, product in zip(result, products): + combination_info = product._get_combination_info(only_template=True) + res_product.update(combination_info) + price_info = self._get_computed_product_price(product, res_product, price_public_visibility, visibility_label, currency_id) + res_product.update(price_info) + res_product['product_variant_id'] = product._get_first_possible_variant_id() + + # Images + res_product['img_small'] = request.website.image_url(product, 'image_256') + res_product['img_medium'] = request.website.image_url(product, 'image_512') + res_product['img_large'] = request.website.image_url(product, 'image_1024') + + # short Description + if 'description_sale' in fields: + description = res_product.get('description_sale') + res_product['short_description'] = description[:125] + '...' if description and len(description) > 125 else description or False + # label and color + if 'colors' in extra_data: + res_product['colors'] = self._get_tp_view_template('theme_prime.product_preview_swatches', {'product': product, '_limit': 4, 'parent_selector': '.card', 'img_selector': '.d-product-img'}) + # label and color + if 'dr_label_id' in fields and product.dr_label_id: + res_product['label'] = product.dr_label_id + res_product['label_id'] = product.dr_label_id.id + res_product['label_template'] = self._get_tp_view_template('theme_prime.product_label', {'label': product.dr_label_id}) + if 'dr_stock_label' in extra_data and showStockLabel and product.dr_show_out_of_stock: + res_product['dr_stock_label'] = self._get_tp_view_template('theme_prime.product_label', {'product': product, 'stock': True}) + res_product['dr_show_out_of_stock'] = product.dr_show_out_of_stock + # rating + if 'offer_data' in extra_data: + offer = product._get_product_pricelist_offer() + if offer: + rule = offer.get('rule') + res_product['offer_data'] = { + 'date_end': offer.get('date_end'), + 'offer_msg': rule.dr_offer_msg, + 'offer_finish_msg': rule.dr_offer_finish_msg + } + + if 'rating' in extra_data: + res_product['rating'] = self._get_rating_template(product.rating_avg) + res_product['rating_avg'] = product.rating_avg + # images + if 'product_variant_ids' in fields: + res_product['images'] = product.product_variant_ids.ids + # website_category + if 'public_categ_ids' in fields and product.public_categ_ids: + first_category = product.public_categ_ids[0] + res_product['category_info'] = { + 'name': first_category.name, + 'id': first_category.id, + 'website_url': '/shop/category/' + str(first_category.id), + } + # brand + if 'dr_brand_value_id' in fields: + res_product['brand_info'] = False + if product.dr_brand_value_id: + res_product['brand_info'] = { + 'name': product.dr_brand_value_id.name, + 'id': product.dr_brand_value_id.id, + } + + return result + + def _get_computed_product_price(self, product, product_data, price_public_visibility, visibility_label, currency_id): + FieldMonetary = request.env['ir.qweb.field.monetary'] + monetary_options = {'display_currency': currency_id} + return { + 'visibility': price_public_visibility, + 'price_raw': product_data['price'] if price_public_visibility else visibility_label, + 'list_price_raw': product_data['list_price'] if price_public_visibility else ' ', + 'price': FieldMonetary.value_to_html(product_data['price'], monetary_options) if price_public_visibility else visibility_label, + 'list_price': FieldMonetary.value_to_html(product_data['list_price'], monetary_options) if price_public_visibility else ' ' + } + + def _get_tp_view_template(self, tmpl, values={}): + IrUiView = request.env['ir.ui.view'] + return IrUiView._render_template(tmpl, values=values) + + def _get_bestseller_products(self, old_limit): + past_date = datetime.datetime.now() - datetime.timedelta(days=30) + result = request.env['sale.report'].sudo().read_group([('date', '>', past_date), ('website_id', '=', request.website.id), ('state', 'in', ['sale', 'done'])], ['product_tmpl_id', 'product_uom_qty:sum'], ['product_tmpl_id'], orderby="product_uom_qty desc") + return [product_line['product_tmpl_id'][0] for product_line in result], None if len(result) else old_limit, None + + def _get_shop_related_data(self, options): + shop_data = {} + if (options.get('shop_config_params')): + shop_data['shop_config_params'] = request.website.get_theme_prime_shop_config() + if (options.get('wishlist_enabled')) and shop_data.get('shop_config_params', {}).get('is_wishlist_active'): + shop_data['wishlist_products'] = request.env['product.wishlist'].with_context(display_default_code=False).current().mapped('product_id').ids + return shop_data + + def _get_rating_template(self, rating_avg, rating_count=False): + return request.website._get_theme_prime_rating_template(rating_avg, rating_count) + + def _get_categories(self, domain=[], fields=['name', 'display_name', 'id'], limit=20, order=None, extras={}): + final_domain = expression.AND([request.website.website_domain(), domain]) + final_list = False + # Pure Shit + if extras.get('count', False): + count_dict = {k: v for k, v in sorted(self.get_categories_count().items(), key=lambda item: item[1], reverse=True)} + if order == 'count': + order = 'sequence asc' + category_ids = count_dict.keys() + final_list = [i for i in category_ids if i not in extras.get('ids', [])][:limit] + final_domain = expression.AND([[['id', 'in', final_list]], final_domain]) + result = request.env['product.public.category'].search_read(final_domain, fields=fields + ['display_name'], limit=limit, order=order) + for res in result: + if extras.get('count', False): + res.update({'count': count_dict.get(res.get('id'), 0)}) + if final_list: + final_result = [] + for res_id in final_list: + for res in result: + if res.get('id') == res_id: + final_result.append(res) + return final_result + return result + + def _get_products_for_top_categories(self, params): + result = {} + categoryIDs = params.get('categoryIDs') + order = params.get('sortBy') + operator = '=' + if params.get('includesChild'): + operator = 'child_of' + initial_domain = expression.AND([request.website.website_domain(), [('website_published', '=', True)]]) + for id in categoryIDs: + domain = expression.AND([initial_domain, [['public_categ_ids', operator, id]]]) + products = self._get_products(domain, ['id'], 4, order) + result[id] = [product['id'] for product in products] + return result + + @http.route('/theme_prime/tp_search_read', type='json', auth='user', website=True, sitemap=False) + def _tp_search_read(self, model, domain=None, fields=[], order=None, limit=20, extras={}, **post): + if model == 'product.template': + if extras.get('show_suggestion', False): + order = 'bestseller' + return self._get_products(domain, fields, limit, order) + elif model == 'product.public.category': + extras.update({'count': True}) + if extras.get('show_suggestion', False): + order = 'count' + return self._get_categories(domain, fields, limit, order, extras) + elif model == 'product.attribute.value': + if extras.get('brands'): + brand_attributes = request.website._get_brand_attributes() + domain = expression.AND([domain, [('attribute_id', 'in', brand_attributes.ids)]]) + return request.env[model].with_context(show_attribute=False).search_read(domain, fields=fields, limit=limit, order=order) + return request.env[model].search_read(domain, fields=fields, limit=limit, order=order) + elif model in ['dr.product.label', 'product.tag']: + return request.env[model].search_read(domain, fields=fields, limit=limit, order=order) + elif model == 'ir.ui.view' and extras.get('templateIDs'): + templateIDs = extras.get('templateIDs') + views = request.env['ir.ui.view'].sudo().search([('key', 'in', templateIDs)]) + Qweb = request.env['ir.qweb'].sudo() + viewData = [] + for tmpl in templateIDs: + view = views.filtered(lambda x: x.key == tmpl) + if view: + view = view[0] + view_keys = view.key.split('.') + imgSrc = view_keys[1] + snippetName = view.name + if extras.get('registryToUse') == 'theme_prime_snippet_registry' and imgSrc == 's_mega_menu_category_tabs_snippet': + imgSrc = 's_tp_categories_menu' + snippetName = 'Categories Menu' + viewData.append({ + 'id': view_keys[1], + 'html': Qweb._render(view.id, {"dr_uid": datetime.datetime.now().microsecond}, minimal_qcontext=True), + 'subtitle': snippetName, + 'imgSrc': '/theme_prime/static/src/img/dynamic_snippet/%s.jpg' % imgSrc, + 'icon': 'theme_prime.icon_grid_1_2', + 'title': 'Choose a Snippet' + }) + return viewData + + @http.route('/theme_prime/get_similar_products_sidebar', type='http', auth='public', website=True, sitemap=False) + def _get_similar_products_sidebar(self, productID, **post): + product_tmpl_id = request.env['product.template'].browse(int(productID)) + return request.render('theme_prime.similar_products_sidebar', {'products': product_tmpl_id.alternative_product_ids}, headers={'Cache-Control': 'no-cache'}) + + @http.route('/theme_prime/get_tab_listing_products', type='json', auth='public', website=True) + def get_tab_listing_products(self, domain=None, fields=[], options={}, limit=25, order=None, **kwargs): + result = {} + if options.get('categoryID', False): + category_domain = [('id', '=', options.get('categoryID'))] + category = self._get_categories(category_domain) + if category: + result['listing_category'] = category + domain = expression.AND([domain, [('public_categ_ids', 'child_of', options.get('categoryID'))]]) + result['products'] = self._get_products(domain, fields, limit, order) + result.update(self._get_shop_related_data(options)) + return result + + @http.route('/theme_prime/get_brands_category_data', type='json', auth='public', website=True) + def get_brands_category_data(self, domain=None, fields=[], options={}): + if options.get('model') in ['product.attribute.value', 'product.public.category']: + if options.get('model') == 'product.attribute.value': + fields += ['attribute_id'] + result = self._tp_search_read(options.get('model'), domain, fields) + if options.get('model') == 'product.attribute.value': + get_brand_count = request.env['product.template']._get_product_attrib_count(attrib_values=[], domain=request.env['website'].sale_product_domain()) + for res in result: + res['count'] = get_brand_count.get(res.get('id')) + return result + + @http.route('/theme_prime/get_listing_products', type='json', auth='public', website=True) + def get_listing_products(self, domain=None, fields=[], options={}, limit=5, **kwargs): + result = {} + # [TO-DO] even snippet don't allow manual selection it will set the attrs + domain = None if options.get('mode') == 'manual' else domain + if options.get('bestseller'): + result['bestseller'] = self._get_products(domain, fields, limit, 'bestseller') + if options.get('newArrived'): + result['newArrived'] = self._get_products(domain, fields, limit, 'create_date desc') + if options.get('discount'): + if domain: + domain = expression.AND([[("dr_has_discount", "!=", False)], domain]) + else: + domain = [["dr_has_discount", "!=", False]] + result['discount'] = self._get_products(domain, fields, limit) + final = {} + final['products'] = result + final.update(self._get_shop_related_data(options)) + return final + + @http.route('/theme_prime/get_products_data', type='json', auth='public', website=True) + def get_products_data(self, domain=None, fields=[], options={}, limit=25, order=None, **kwargs): + result = { + 'products': self._get_products(domain, fields, limit, order), + } + result.update(self._get_shop_related_data(options)) + return result + + @http.route('/theme_prime/get_products_by_category', type='json', auth='public', website=True) + def get_products_by_category(self, domain, fields=[], options={}, **kwargs): + final_domain = expression.AND([[('website_published', '=', True)], domain]) + result = { + 'products': self._get_products(domain=final_domain, fields=fields, order=options.get('order', False), limit=options.get('limit', False)), + } + result.update(self._get_shop_related_data(options)) + if (options.get('get_categories')): + # get category names for snippet + domain = [('id', 'in', options.get('categoryIDs'))] + result['categories'] = self._get_categories(domain) + if (options.get('get_brands')): + # get category names for snippet + domain = [('id', 'in', options.get('categoryIDs'))] + result['categories'] = request.website._get_brands(domain).read(['name', 'id']) + return result + + @http.route('/theme_prime/get_top_categories', type='json', auth='public', website=True) + def get_top_categories(self, options={}): + params = options.get('params') + result = [] + website_sale_domain = request.website.sale_product_domain() + if params: + categoryIDs = params.get('categoryIDs') + if categoryIDs: + domain = [('id', 'in', categoryIDs)] + category_names = {i['id']: i['name'] for i in self._get_categories(domain)} + # Update categoryIDs if already set category moved to other website + categoryIDs = category_names.keys() + params['categoryIDs'] = categoryIDs + categories = self._get_products_for_top_categories(params) + price_public_visibility = request.website._dr_has_b2b_access() + for category_id in categoryIDs: + category_data = {} + product_ids = categories.get(category_id) + category_data['name'] = category_names.get(category_id) + category_data['id'] = category_id + category_data['website_url'] = '/shop/category/' + str(category_id) + category_data['productIDs'] = product_ids + final_domain = expression.AND([website_sale_domain, [('public_categ_ids', 'child_of', category_id)]]) + products = self._get_products(domain=final_domain, fields=[], limit=1, order="list_price asc") + if len(products): + category_data['min_price'] = products[0].get('price') + category_data['price_public_visibility'] = price_public_visibility + result.append(category_data) + return result + + @http.route(['/theme_prime/get_dialog_content'], type='json', website=True, auth="public") + def get_dialog_content(self, res_id, res_model, fields, **post): + return request.env[res_model].sudo().search_read([('id', '=', res_id)], fields=fields) + + @http.route('/theme_prime/get_categories_count', type='json', auth='public', website=True) + def get_categories_count(self): + return request.env['product.template']._get_product_category_count(domain=request.env['website'].sale_product_domain()) + + @http.route('/theme_prime/get_categories_info', type='json', auth='public', website=True) + def get_categories_info(self, fields=[], options={}, **kwargs): + categoryIDs = options.get('categoryIDs', []) + fields = ['name', 'display_name', 'id'] + fields + domain = expression.AND([request.website.website_domain(), [('id', 'in', categoryIDs)]]) + categories = request.env['product.public.category'].search(domain) + result = categories.read(fields) + if options.get('getCount', False): + get_category_count = self.get_categories_count() + for res_category, category in zip(result, categories): + if 'dr_category_label_id' in fields and category.dr_category_label_id: + category_label = category.dr_category_label_id + res_category['category_lable_info'] = { + 'id': category_label.id, + 'name': category_label.name, + 'background_color': category_label.background_color, + 'text_color': category_label.text_color, + } + if options.get('getCount', False): + res_category['count'] = get_category_count.get(category.id, 0) + res_category['website_url'] = '/shop/category/' + str(category.id) + res_category['image_url'] = '/web/image?model=product.public.category&id=%d&field=image_512' % (category.id) + res_category['cover_image'] = '/web/image?model=product.public.category&id=%d&field=dr_category_cover_image' % (category.id) + return result + + @http.route('/theme_prime/get_brands', type='json', auth='public', website=True) + def get_brands(self, fields=['id', 'name', 'attribute_id'], options={}): + if options.get('categories'): + domain = expression.AND([request.website.sale_product_domain(), [('public_categ_ids', 'child_of', options.get('categories'))]]) + brands = request.env['product.template'].search(domain).mapped('dr_brand_value_id')[:options.get('limit', 12)] + if options.get('recordsIDs'): + brands = request.website._get_brands([('id', 'in', options.get('recordsIDs'))], options.get('limit', 12)) + domain = request.env['website'].sale_product_domain() + get_attrib_count = request.env['product.template']._get_product_attrib_count(attrib_values=[], domain=domain) + return [{**brand_data, 'product_count': get_attrib_count.get(brand_data['id'], 0)} for brand_data in brands.read(fields)] + + @http.route('/theme_prime/get_preview_body', type='http', auth="user", website=True, sitemap=False, multilang=False) + def get_preview_body(self, **kw): + return request.env['ir.ui.view']._render_template('theme_prime.tp_preview_body', {'mobile': kw.get('mobile')}) + + @http.route('/theme_prime/get_megamenu_categories', type='json', auth='public', website=True) + def get_megamenu_categories(self, options={}, limit=5, fields=['name', 'id'], order='count', **kwargs): + category_ids = request.env['product.public.category'].browse(options.get('categoryIDs', [])).exists().ids + final_domain = expression.AND([request.website.website_domain(), [('parent_id', 'child_of', category_ids)]]) + categories = request.env['product.public.category'].search(final_domain, limit=None) + + all_categories = [] + all_category_count = request.env['product.template']._get_product_category_count(request.env['website'].sale_product_domain()) + for category in categories: + all_categories.append({ + 'id': category.id, 'name': category.name, 'parent_id': category.parent_id.id, + 'count': all_category_count.get(category['id'], 0), + 'website_url': '/shop/category/' + str(category.id), + 'image_url': '/web/image?model=product.public.category&id=%d&field=image_512' % (category.id), + 'cover_image': '/web/image?model=product.public.category&id=%d&field=dr_category_cover_image' % (category.id), + 'dr_category_icon': '/web/image?model=product.public.category&id=%d&field=dr_category_icon' % (category.id), + 'category_label_info': category.dr_category_label_id and {f: category.dr_category_label_id[f] for f in ['name', 'background_color', 'text_color']} or False, + }) + + parent_categories = filter(lambda category: category.get('id') in category_ids, all_categories) + return [self._get_megamenu_child_categories(category_id, limit, all_categories, order, options) for category_id in parent_categories] + + def _get_megamenu_child_categories(self, parent_category, limit, all_categories, order, options): + child_categories = [categ for categ in all_categories if categ.get('parent_id') == parent_category.get('id')] + if not child_categories: + return {'category': parent_category, 'child': []} + if order == 'count' or not order: + child_categories = sorted(child_categories, key=lambda category: category.get('count', 0), reverse=True) + child_categories = child_categories[:limit] + remain_limit = limit - len(child_categories) + + if remain_limit <= 0 or options.get('onlyDirectChild', False): + return {'category': parent_category, 'child': child_categories} + for child_category in child_categories[:]: + new_born_child = self._get_megamenu_child_categories(child_category, remain_limit, all_categories, order, options).get('child') + child_categories.extend(new_born_child) + remain_limit = limit - len(child_categories) + if remain_limit <= 0: + break + return {'category': parent_category, 'child': child_categories} + + +class ThemeWebsite(Website): + + @http.route('/website/dr_search', type='json', auth="public", website=True, sitemap=False) + def dr_search(self, term, max_nb_chars, options, **kw): + + fuzzy_term, global_match = False, False + search_config = request.website._get_dr_theme_config('json_product_search') + has_formulate = self._dr_has_formulate(search_config) + fuzzy_enabled = search_config.get('search_fuzzy') + limit = max(min(search_config.get('search_limit'), 10), 5) + search_types = ['products', 'categories', 'autocomplete', 'suggestions'] + results = {search_type: {'results': [], 'results_count': 0, 'parts': {}} for search_type in search_types} + product_limit = max(min(search_config.get('search_max_product'), 5), 0) + options = {'allowFuzzy': fuzzy_enabled, 'displayDescription': False, 'displayDetail': True, 'displayExtraLink': True, 'displayImage': True} + if product_limit: + results['products'] = self.autocomplete(search_type='products_only', term=term, order='name asc', limit=product_limit, options=options) + product_fuzzy_term = results['products'].get('fuzzy_search') + + if search_config.get('search_category') and not has_formulate: + results['categories'] = self.autocomplete(search_type='product_categories_only', term=term, order='sequence, name, id', limit=5, options=options) + category_fuzzy_term = results['categories'].get('fuzzy_search') + if fuzzy_enabled: + empty_search = {'results': [], 'results_count': 0, 'parts': {}} + if category_fuzzy_term == product_fuzzy_term: + fuzzy_term = product_fuzzy_term + elif not category_fuzzy_term and results['categories'].get('results_count'): + results['products'], fuzzy_term = empty_search, False + elif not product_fuzzy_term and results['products'].get('results_count'): + results['categories'], fuzzy_term = empty_search, False + elif product_fuzzy_term and not category_fuzzy_term: # category will be always empty based on above conditions + fuzzy_term = product_fuzzy_term + elif category_fuzzy_term and not product_fuzzy_term: # products will be always empty based on above conditions + fuzzy_term = category_fuzzy_term + else: # super rare case + all_results = self.autocomplete(search_type='products', term=term, order='sequence, name, id', limit=limit, options=options) + products_result = [res for res in all_results['results'] if res.get('_fa') == 'fa-shopping-cart'] + category_result = [res for res in all_results['results'] if res.get('_fa') == 'fa-folder-o'] + fuzzy_term = all_results.get('fuzzy_search') + results = {'products': {'results': products_result, 'results_count': len(products_result), 'parts': {}}, 'categories': {'results': category_result, 'results_count': len(category_result), 'parts': {}}} + + # Search from category synonyms if no category found + if results.get('categories') and not results.get('categories').get('results_count'): + category_result = self.autocomplete(search_type='category_synonyms', term=term, order='name asc', limit=5, options=options) + results['categories'] = {'results': category_result['results'], 'results_count': len(category_result['results']), 'parts': {}} + + # suggestion search + if search_config.get('search_attribute') or search_config.get('search_suggestion'): + remain_limit = limit - min(product_limit, results['products'].get('results_count', 0)) # Odoo results_count returns count for full result (without limit) + words = [i for i in term.split(' ') if i] # split and filter spaces + matchs, matched_dicts = False, {} + for word in words: + if matchs: + for match in matchs: + match_dict = matched_dicts[match] + if match_dict['remaining_words']: + match_dict['remaining_words'].append(word) + else: + unmatched_record_name = match_dict['unmatched_record_name'] + regex_match = re.search(re.escape(word), unmatched_record_name, re.IGNORECASE) + if regex_match: + match_dict['matched_words'].append(word) + match_dict['unmatched_record_name'] = re.sub(re.escape(word), ' ', unmatched_record_name, flags=re.I) + else: + match_dict['remaining_words'].append(word) + else: + matchs = self._match_attr_or_category(word) + if matchs: + for match in matchs: + matched_dicts[match] = match_dict = {'match': match, 'matched_words': [word], 'remaining_words': []} + match_dict['unmatched_record_name'] = re.sub(re.escape(match_dict['matched_words'][0]), ' ', match.ds_name, flags=re.I) + + match_list_raw = list(matched_dicts.values()) + match_list_raw.sort(key=lambda m: len(m['matched_words']), reverse=True) + + # filter matched result and remove the duplicate matches from the synonyms + match_list, matched_ids = [], [] + for matched_dict in match_list_raw: + matched_record = matched_dict['match'] + if matched_record.id not in matched_ids: + matched_ids.append(matched_record.id) + match_list.append(matched_dict) + + autocomplete_result = [] + + for match_dict in match_list: + autocomplete_data = [] + if match_dict['remaining_words']: + autocomplete_data = self._get_autocomplete_data(match_dict, remain_limit, search_config) + elif not search_config.get('search_category') and match_dict['match']._name == 'product.public.category': + autocomplete_result.append(self.generate_result_dict(match_dict['match'], False, match_dict['matched_words'], '')) + remain_limit -= 1 + if not remain_limit: + break + + if not match_dict['remaining_words']: + # if no remaining_words that means full data matched with record so suggestions become autocomplete + autocomplete_data += self._get_suggestions_data(match_dict, autocomplete_result, remain_limit, search_config, ignore_config=True) + remain_limit -= len(autocomplete_data) + autocomplete_result.extend(autocomplete_data) + if not remain_limit: + break + + suggestions_result = [] + for match_dict in match_list: + suggestions_data = self._get_suggestions_data(match_dict, autocomplete_result, min(remain_limit, 5), search_config) + remain_limit -= len(suggestions_data) + suggestions_result.extend(suggestions_data) + if not remain_limit: + break + + results['autocomplete'] = {'results': autocomplete_result, 'results_count': len(autocomplete_result), "parts": {"name": True, "website_url": True}} + results['suggestions'] = {'results': suggestions_result, 'results_count': len(suggestions_result), "parts": {"name": True, "website_url": True}} + + global_match = False + if matchs and len(matchs) == 1 and (results['autocomplete'].get('results_count') or results['suggestions'].get('results_count')): + if matchs[0]._name == 'product.public.category': + fixed_str = _('View all products with category') + global_match = {'name': f'{fixed_str} {matchs[0].ds_name}', 'website_url': f'/shop?category={matchs[0].id}'} + else: + fixed_str = _('View all products with') + global_match = {'name': f'{fixed_str} {matchs.attribute_id.name.lower()} {matchs.ds_name}', 'website_url': f'/shop?&attrib={matchs.attribute_id.id}-{matchs.id}'} + + # fill in the extra products is keyword does not match with normal products + if search_config.get('search_fill_products') and results['products']['results_count'] == 0 and results['autocomplete']['results_count'] > 0: + domain_all = [] + for autocomplete_dict in results['autocomplete']['results'][:1]: + domain, catgory_id, attribute_value_od = [], autocomplete_dict.get('category'), autocomplete_dict.get('attribute') + if catgory_id: + domain.append(('public_categ_ids', 'child_of', [catgory_id])) + if attribute_value_od: + domain.append(('attribute_line_ids.value_ids', 'in', [attribute_value_od])) + domain_all.append(domain) + # TODO: Improve this. Here is the simple hack to trick autocomplete and ignore search term use our domain, This will save tons of extra code + options['dr_search_domain'] = expression.OR(domain_all) + results['products'] = self.autocomplete(search_type='products_only', term=term, order='name asc', limit=product_limit, options=options) + + search_result = {**results, 'fuzzy_search': fuzzy_term, 'results': [], 'global_match': global_match, 'result_length': sum([results.get(r_type, {}).get('results_count', 0) for r_type in search_types])} + + if search_config.get('search_report') and search_result["result_length"] == 0: + report_data = { + 'search_term': term, + 'device_type': kw.get('device_type') + } + request.env['dr.search.report']._add_report_entry(report_data) + + return search_result + + def _get_autocomplete_data(self, match_dict, remain_limit, search_config): + match, remaining_words, matched_words = match_dict['match'], match_dict['remaining_words'], match_dict['matched_words'] + results = [] + if search_config.get('search_attribute') and remaining_words and match: + for related_match, word in self.match_remaining_words(match, remaining_words): + results.append(self.generate_result_dict(match, related_match, matched_words, word)) + matched_words.append(word) + if len(results) >= remain_limit: + break + return results + + def _get_suggestions_data(self, match_dict, autocomplete_data, remain_limit, search_config, ignore_config=False): + results = [] + match, matched_words = match_dict['match'], match_dict['matched_words'] + if (search_config.get('search_suggestion') or ignore_config) and remain_limit > 0: + if match._name == 'product.public.category': + for related_match in self._category_counterpart_iterator(match, search_type=['auto_suggestion']): + term = self.generate_result_dict(match, related_match, matched_words) + if not self.is_search_added(autocomplete_data, results, term): + results.append(term) + if len(results) >= remain_limit: + break + else: + domain = request.website.sale_product_domain() + [('attribute_line_ids.value_ids', 'in', match.ids)] + all_related_records = request.env['product.template'].with_context(bin_size=True).search(domain).mapped('public_categ_ids') + for related_match in all_related_records: + term = self.generate_result_dict(match, related_match, matched_words) + if not self.is_search_added(autocomplete_data, results, term): + results.append(term) + if len(results) >= remain_limit: + break + return results + + def _match_attr_or_category(self, term, return_on_match=True): + result = self._match_category(term=term) + if not result: + result = self._match_attr(term) + return result + + def _match_category(self, term=False, categories_ids=[]): + CSD = request.env['product.public.category']._get_search_db(request.website) + return CSD.search(term=term, categories_ids=categories_ids) + + def _match_attr(self, term): + all_active_attributes = self._website_active_attributes() + matched_values = request.env['product.attribute.value'] + if all_active_attributes: + matched_values = matched_values.search([('ds_name', 'ilike', term), ('attribute_id', 'in', all_active_attributes.ids)]) + return matched_values + + def _website_active_attributes(self): + all_products = request.env['product.template'].with_context(bin_size=True).search(request.website.sale_product_domain()) + return request.env['product.attribute'].search([ + ('product_tmpl_ids', 'in', all_products.ids), ('visibility', '=', 'visible'), ('dr_search_suggestion', '!=', False) + ]) + + def match_remaining_words(self, match, remaining_words): + if match._name == 'product.public.category': + for word in remaining_words: + for attribute_value in self._category_counterpart_iterator(match): + regex_match = re.search(re.escape(word), attribute_value.ds_name, re.IGNORECASE) + if regex_match: + yield attribute_value, word + + if match._name == 'product.attribute.value': + domain = request.website.sale_product_domain() + [('attribute_line_ids.value_ids', 'in', match.ids)] + related_categories = request.env['product.template'].with_context(bin_size=True).search(domain).mapped('public_categ_ids') + categories = self._match_category(categories_ids=related_categories.ids) + for word in remaining_words: + for category in categories: + regex_match = re.search(re.escape(word), category.ds_name, re.IGNORECASE) + if regex_match: + yield category, word + + def _category_counterpart_iterator(self, category_dst, search_type=['auto_suggestion', 'auto']): + category = request.env['product.public.category'].browse(category_dst.id) + attribute_values = category.mapped('product_tmpl_ids.attribute_line_ids').filtered(lambda line: line.attribute_id.dr_search_suggestion in search_type).mapped('value_ids') + for value in attribute_values: + yield value + + # Child category + child_categories = request.env['product.public.category'].search([('parent_id', 'child_of', category.id)]) - category + child_attribute_values = child_categories.mapped('product_tmpl_ids.attribute_line_ids').filtered(lambda line: line.attribute_id.dr_search_suggestion in search_type).mapped('value_ids') # plus to maintain order + for value in child_attribute_values: + if value not in attribute_values: + yield value + + def generate_result_dict(self, primary_match, secondary_match, matched_words, word=False): + category, attribute = (primary_match, secondary_match) if primary_match._name == 'product.public.category' else (secondary_match, primary_match) + attribute_str = f"&attrib={attribute.attribute_id.id}-{attribute.id}" if attribute else '' # just for category + return { + 'name': self.format_result(matched_words + (word and [word] or []), f"{primary_match.ds_name} {secondary_match and secondary_match.ds_name or ''}"), + 'website_url': f"/shop?category={category.id}{attribute_str}", + 'category': category and category.id, + 'attribute': attribute and attribute.id + } + + def format_result(self, matched_words, value): + pattern = '|'.join(map(re.escape, matched_words)) + parts = re.split(f'({pattern})', value, flags=re.IGNORECASE) + if len(parts) > 1: + value = request.env['ir.ui.view'].sudo()._render_template("website.search_text_with_highlight", {'parts': parts}) + html_val = request.env[('ir.qweb.field.html')].value_to_html(value, {'template_options': {}}) + return html_escape(html_val) + return False + + def is_search_added(self, autocomplete_result, suggestions_results, new_term): + auto_found = len([term for term in autocomplete_result if new_term['website_url'] == term['website_url']]) + sugg_found = len([term for term in suggestions_results if new_term['website_url'] == term['website_url']]) + return auto_found + sugg_found + + def _dr_has_formulate(self, search_config): # for performance + if search_config.get('search_attribute') or search_config.get('search_suggestion'): + formulate_category = request.env['product.public.category'].search(([('dr_search_formulate', '=', True)] + request.website.website_domain()), limit=1) + if formulate_category: + request.update_context(dr_formulate=True) + return len(formulate_category) + return False + + @http.route('/website/dr_search/add_report', type='json', auth="public", website=True, sitemap=False) + def dr_search_report(self, **kw): + request.env['dr.search.report']._add_report_entry(kw) + return {} + + +class ThemePrimeBrand(http.Controller): + + @http.route('/shop/all-brands', type='http', auth='public', website=True, sitemap=True) + def brands(self, search='', **args): + is_disable_grouping = request.website._get_dr_theme_config('json_brands_page')['disable_brands_grouping'] + brands = request.website._get_brands([('name', 'ilike', search)] if search else []) + + grouped_brands = defaultdict(list) + if is_disable_grouping: + grouped_brands = {'All Brands': brands} + else: + alphabet_range = string.ascii_uppercase + grouped_brands.update((alphabet, []) for alphabet in alphabet_range) + for brand in brands: + first_char = str.upper(brand.name[:1]) + grouped_brands[first_char].append(brand) + + get_brand_count = request.env['product.template']._get_product_attrib_count(attrib_values=[], domain=request.env['website'].sale_product_domain()) + return request.render('theme_prime.all_brands', {'is_disable_grouping': is_disable_grouping, 'brands': brands, 'grouped_brands': grouped_brands, 'search': search, 'get_brand_count': get_brand_count}) + + +class ThemePrimePWA(http.Controller): + + @http.route('/pwa//manifest.json', type='http', auth='public', website=True) + def get_pwa_manifest(self, website_id, **kargs): + manifest_data = {"fake": 1} + website = request.website + if website and website.id == website_id and website.dr_pwa_activated: + manifest_data = { + "name": website.dr_pwa_name, + "short_name": website.dr_pwa_short_name, + "display": "standalone", + "background_color": website.dr_pwa_background_color, + "theme_color": website.dr_pwa_theme_color, + "id": website.dr_pwa_start_url, + "start_url": website.dr_pwa_start_url, + "scope": "/", + "icons": [{ + "src": "/web/image/website/%s/dr_pwa_icon_192/192x192" % website.id, + "sizes": "192x192", + "type": "image/png", + }, { + "src": "/web/image/website/%s/dr_pwa_icon_512/512x512" % website.id, + "sizes": "512x512", + "type": "image/png", + }] + } + if website.dr_pwa_screenshots: + manifest_data['screenshots'] = [{ + "src": "/web/image/dr.pwa.screenshots/%s/image" % screenshot.id, + "type": "image/jpg", + "sizes": screenshot.sizes, + "form_factor": screenshot.form_factor, + } for screenshot in website.dr_pwa_screenshots] + if website.dr_pwa_shortcuts: + manifest_data['shortcuts'] = [{ + "name": shortcut.name, + "short_name": shortcut.short_name or '', + "description": shortcut.description or '', + "url": shortcut.url, + "icons": [{"src": "/web/image/dr.pwa.shortcuts/%s/icon/192x192" % shortcut.id, "sizes": "192x192"}] + } for shortcut in website.dr_pwa_shortcuts] + return request.make_response( + data=json.dumps(manifest_data), + headers=[('Content-Type', 'application/json')] + ) + + @http.route('/service_worker.js', type='http', auth='public', website=True, sitemap=False) + def get_pwa_service_worker(self, **kargs): + website = request.website + offline_bool = 'true' if website.dr_pwa_offline_page else 'false' + data = file_open('theme_prime/static/src/js/service_worker.js', 'rb').read().decode() + data = data.replace('"##1##"', str(website.dr_pwa_version)) + data = data.replace('"##2##"', offline_bool) + + return request.make_response( + data=data, + headers=[('Content-Type', 'text/javascript')] + ) + + @http.route('/pwa/offline_page', type='http', auth='public', website=True, cors='*', sitemap=False) + def get_pwa_offline_page(self, **kargs): + return request.render('theme_prime.pwa_offline_page', {}) + + @http.route('/pwa/logo.png', type='http', auth='public', website=True, cors='*', sitemap=False) + def get_pwa_logo(self, **kargs): + website = request.website + imgname = 'logo' + imgext = '.png' + if not website.logo: + response = http.Stream.from_path(file_path('web/static/img/nologo.png')).get_response() + else: + image_base64 = base64.b64decode(website.logo) + image_data = io.BytesIO(image_base64) + mimetype = guess_mimetype(image_base64, default='image/png') + imgext = '.' + mimetype.split('/')[1] + if imgext == '.svg+xml': + imgext = '.svg' + response = send_file(image_data, request.httprequest.environ, download_name=imgname + imgext, mimetype=mimetype, last_modified=website.write_date) + return response diff --git a/theme_prime/data/theme.ir.attachment.csv b/theme_prime/data/theme.ir.attachment.csv new file mode 100644 index 000000000..38475bc4d --- /dev/null +++ b/theme_prime/data/theme.ir.attachment.csv @@ -0,0 +1,117 @@ +id,key,name,url +s_banner_1_1,theme_prime.s_banner_1_1,theme_prime.s_banner_1_1,/theme_prime/static/src/img/snippets/s_banner_1_1.jpg +s_banner_1_2,theme_prime.s_banner_1_2,theme_prime.s_banner_1_2,/theme_prime/static/src/img/snippets/s_banner_1_2.jpg +s_banner_1_3,theme_prime.s_banner_1_3,theme_prime.s_banner_1_3,/theme_prime/static/src/img/snippets/s_banner_1_3.jpg +s_banner_3_1,theme_prime.s_banner_3_1,theme_prime.s_banner_3_1,/theme_prime/static/src/img/snippets/s_banner_3_1.jpg +s_banner_3_2,theme_prime.s_banner_3_2,theme_prime.s_banner_3_2,/theme_prime/static/src/img/snippets/s_banner_3_2.jpg +s_banner_3_3,theme_prime.s_banner_3_3,theme_prime.s_banner_3_3,/theme_prime/static/src/img/snippets/s_banner_3_3.jpg +s_banner_4_1,theme_prime.s_banner_4_1,theme_prime.s_banner_4_1,/theme_prime/static/src/img/snippets/s_banner_4_1.jpg +s_banner_4_2,theme_prime.s_banner_4_2,theme_prime.s_banner_4_2,/theme_prime/static/src/img/snippets/s_banner_4_2.jpg +s_banner_4_3,theme_prime.s_banner_4_3,theme_prime.s_banner_4_3,/theme_prime/static/src/img/snippets/s_banner_4_3.jpg +s_banner_5_1,theme_prime.s_banner_5_1,theme_prime.s_banner_5_1,/theme_prime/static/src/img/snippets/s_banner_5_1.png +s_banner_5_2,theme_prime.s_banner_5_2,theme_prime.s_banner_5_2,/theme_prime/static/src/img/snippets/s_banner_5_2.png +s_banner_6_1,theme_prime.s_banner_6_1,theme_prime.s_banner_6_1,/theme_prime/static/src/img/snippets/s_banner_6_1.jpg +s_banner_6_2,theme_prime.s_banner_6_2,theme_prime.s_banner_6_2,/theme_prime/static/src/img/snippets/s_banner_6_2.jpg +s_banner_6_3,theme_prime.s_banner_6_3,theme_prime.s_banner_6_3,/theme_prime/static/src/img/snippets/s_banner_6_3.jpg +s_brand_1,theme_prime.s_brand_1,theme_prime.s_brand_1,/theme_prime/static/src/img/snippets/s_brand_1.png +s_brand_2,theme_prime.s_brand_2,theme_prime.s_brand_2,/theme_prime/static/src/img/snippets/s_brand_2.png +s_brand_3,theme_prime.s_brand_3,theme_prime.s_brand_3,/theme_prime/static/src/img/snippets/s_brand_3.png +s_banner_7_1,theme_prime.s_banner_7_1,theme_prime.s_banner_7_1,/theme_prime/static/src/img/snippets/s_banner_7_1.jpg +s_banner_7_2,theme_prime.s_banner_7_2,theme_prime.s_banner_7_2,/theme_prime/static/src/img/snippets/s_banner_7_2.jpg +s_banner_7_3,theme_prime.s_banner_7_3,theme_prime.s_banner_7_3,/theme_prime/static/src/img/snippets/s_banner_7_3.jpg +s_banner_8_1,theme_prime.s_banner_8_1,theme_prime.s_banner_8_1,/theme_prime/static/src/img/snippets/s_banner_8_1.jpg +s_banner_8_2,theme_prime.s_banner_8_2,theme_prime.s_banner_8_2,/theme_prime/static/src/img/snippets/s_banner_8_2.jpg +s_banner_8_3,theme_prime.s_banner_8_3,theme_prime.s_banner_8_3,/theme_prime/static/src/img/snippets/s_banner_8_3.jpg +s_banner_9_1,theme_prime.s_banner_9_1,theme_prime.s_banner_9_1,/theme_prime/static/src/img/snippets/s_banner_9_1.jpg +s_banner_9_2,theme_prime.s_banner_9_2,theme_prime.s_banner_9_2,/theme_prime/static/src/img/snippets/s_banner_9_2.jpg +s_banner_9_3,theme_prime.s_banner_9_3,theme_prime.s_banner_9_3,/theme_prime/static/src/img/snippets/s_banner_9_3.jpg +s_banner_10_1,theme_prime.s_banner_10_1,theme_prime.s_banner_10_1,/theme_prime/static/src/img/snippets/s_banner_10_1.png +s_banner_12_1,theme_prime.s_banner_12_1,theme_prime.s_banner_12_1,/theme_prime/static/src/img/snippets/s_banner_12_1.jpg +s_banner_12_2,theme_prime.s_banner_12_2,theme_prime.s_banner_12_2,/theme_prime/static/src/img/snippets/s_banner_12_2.jpg +s_banner_13_1,theme_prime.s_banner_13_1,theme_prime.s_banner_13_1,/theme_prime/static/src/img/snippets/s_banner_13_1.jpg +s_banner_13_2,theme_prime.s_banner_13_2,theme_prime.s_banner_13_2,/theme_prime/static/src/img/snippets/s_banner_13_2.jpg +s_banner_13_3,theme_prime.s_banner_13_3,theme_prime.s_banner_13_3,/theme_prime/static/src/img/snippets/s_banner_13_3.jpg +s_banner_13_4,theme_prime.s_banner_13_4,theme_prime.s_banner_13_4,/theme_prime/static/src/img/snippets/s_banner_13_4.jpg +s_banner_14_1,theme_prime.s_banner_14_1,theme_prime.s_banner_14_1,/theme_prime/static/src/img/snippets/s_banner_14_1.png +s_banner_14_2,theme_prime.s_banner_14_2,theme_prime.s_banner_14_2,/theme_prime/static/src/img/snippets/s_banner_14_2.png +s_banner_15_1,theme_prime.s_banner_15_1,theme_prime.s_banner_15_1,/theme_prime/static/src/img/snippets/s_banner_15_1.jpg +s_client_01,theme_prime.s_client_01,theme_prime.s_client_01,/theme_prime/static/src/img/snippets/s_client_01.png +s_client_02,theme_prime.s_client_02,theme_prime.s_client_02,/theme_prime/static/src/img/snippets/s_client_02.png +s_client_03,theme_prime.s_client_03,theme_prime.s_client_03,/theme_prime/static/src/img/snippets/s_client_03.png +s_client_04,theme_prime.s_client_04,theme_prime.s_client_04,/theme_prime/static/src/img/snippets/s_client_04.png +s_client_05,theme_prime.s_client_05,theme_prime.s_client_05,/theme_prime/static/src/img/snippets/s_client_05.png +s_client_06,theme_prime.s_client_06,theme_prime.s_client_06,/theme_prime/static/src/img/snippets/s_client_06.png +s_client_07,theme_prime.s_client_07,theme_prime.s_client_07,/theme_prime/static/src/img/snippets/s_client_07.png +s_client_08,theme_prime.s_client_08,theme_prime.s_client_08,/theme_prime/static/src/img/snippets/s_client_08.png +s_client_09,theme_prime.s_client_09,theme_prime.s_client_09,/theme_prime/static/src/img/snippets/s_client_09.png +s_client_10,theme_prime.s_client_10,theme_prime.s_client_10,/theme_prime/static/src/img/snippets/s_client_10.png +s_client_11,theme_prime.s_client_11,theme_prime.s_client_11,/theme_prime/static/src/img/snippets/s_client_11.png +s_client_12,theme_prime.s_client_12,theme_prime.s_client_12,/theme_prime/static/src/img/snippets/s_client_12.png +s_coming_soon_2_1,theme_prime.s_coming_soon_2_1,theme_prime.s_coming_soon_2_1,/theme_prime/static/src/img/snippets/s_coming_soon_2_1.png +s_coming_soon_2_2,theme_prime.s_coming_soon_2_2,theme_prime.s_coming_soon_2_2,/theme_prime/static/src/img/snippets/s_coming_soon_2_2.jpg +s_info_block_1_1,theme_prime.s_info_block_1_1,theme_prime.s_info_block_1_1,/theme_prime/static/src/img/snippets/s_info_block_1_1.jpg +s_info_block_2_1,theme_prime.s_info_block_2_1,theme_prime.s_info_block_2_1,/theme_prime/static/src/img/snippets/s_info_block_2_1.jpg +s_info_block_3_1,theme_prime.s_info_block_3_1,theme_prime.s_info_block_3_1,/theme_prime/static/src/img/snippets/s_info_block_3_1.jpg +s_info_block_3_2,theme_prime.s_info_block_3_2,theme_prime.s_info_block_3_2,/theme_prime/static/src/img/snippets/s_info_block_3_2.jpg +s_info_block_4_1,theme_prime.s_info_block_4_1,theme_prime.s_info_block_4_1,/theme_prime/static/src/img/snippets/s_info_block_4_1.jpg +s_info_block_4_2,theme_prime.s_info_block_4_2,theme_prime.s_info_block_4_2,/theme_prime/static/src/img/snippets/s_info_block_4_2.jpg +s_info_block_4_3,theme_prime.s_info_block_4_3,theme_prime.s_info_block_4_3,/theme_prime/static/src/img/snippets/s_info_block_4_3.jpg +s_info_block_5_1,theme_prime.s_info_block_5_1,theme_prime.s_info_block_5_1,/theme_prime/static/src/img/snippets/s_info_block_5_1.jpg +s_info_block_5_2,theme_prime.s_info_block_5_2,theme_prime.s_info_block_5_2,/theme_prime/static/src/img/snippets/s_info_block_5_2.jpg +s_info_block_6_1,theme_prime.s_info_block_6_1,theme_prime.s_info_block_6_1,/theme_prime/static/src/img/snippets/s_info_block_6_1.jpg +s_info_block_7_1,theme_prime.s_info_block_7_1,theme_prime.s_info_block_7_1,/theme_prime/static/src/img/snippets/s_info_block_7_1.jpeg +s_info_block_8_1,theme_prime.s_info_block_8_1,theme_prime.s_info_block_8_1,/theme_prime/static/src/img/snippets/s_info_block_8_1.png +s_cover_1_1,theme_prime.s_cover_1_1,theme_prime.s_cover_1_1,/theme_prime/static/src/img/snippets/s_cover_1_1.png +s_cover_1_2,theme_prime.s_cover_1_2,theme_prime.s_cover_1_2,/theme_prime/static/src/img/snippets/s_cover_1_2.png +s_cover_1_brand_1,theme_prime.s_cover_1_brand_1,theme_prime.s_cover_1_brand_1,/theme_prime/static/src/img/snippets/s_cover_1_brand_1.png +s_cover_1_brand_2,theme_prime.s_cover_1_brand_2,theme_prime.s_cover_1_brand_2,/theme_prime/static/src/img/snippets/s_cover_1_brand_2.png +s_cover_2_1,theme_prime.s_cover_2_1,theme_prime.s_cover_2_1,/theme_prime/static/src/img/snippets/s_cover_2_1.jpg +s_cover_2_2,theme_prime.s_cover_2_2,theme_prime.s_cover_2_2,/theme_prime/static/src/img/snippets/s_cover_2_2.jpg +s_cover_3_1,theme_prime.s_cover_3_1,theme_prime.s_cover_3_1,/theme_prime/static/src/img/snippets/s_cover_3_1.jpg +s_cover_4_1,theme_prime.s_cover_4_1,theme_prime.s_cover_4_1,/theme_prime/static/src/img/snippets/s_cover_4_1.png +s_cover_4_2,theme_prime.s_cover_4_2,theme_prime.s_cover_4_2,/theme_prime/static/src/img/snippets/s_cover_4_2.png +s_cover_4_3,theme_prime.s_cover_4_3,theme_prime.s_cover_4_3,/theme_prime/static/src/img/snippets/s_cover_4_3.png +s_cover_4_4,theme_prime.s_cover_4_4,theme_prime.s_cover_4_4,/theme_prime/static/src/img/snippets/s_cover_4_4.png +s_cover_4_5,theme_prime.s_cover_4_5,theme_prime.s_cover_4_5,/theme_prime/static/src/img/snippets/s_cover_4_5.png +s_call_to_action_5_1,theme_prime.s_call_to_action_5_1,theme_prime.s_call_to_action_5_1,/theme_prime/static/src/img/snippets/s_call_to_action_5_1.jpg +s_gallery_01,theme_prime.s_gallery_01,theme_prime.s_gallery_01,/theme_prime/static/src/img/snippets/s_gallery_01.jpg +s_gallery_02,theme_prime.s_gallery_02,theme_prime.s_gallery_02,/theme_prime/static/src/img/snippets/s_gallery_02.jpg +s_gallery_03,theme_prime.s_gallery_03,theme_prime.s_gallery_03,/theme_prime/static/src/img/snippets/s_gallery_03.jpg +s_gallery_04,theme_prime.s_gallery_04,theme_prime.s_gallery_04,/theme_prime/static/src/img/snippets/s_gallery_04.jpg +s_gallery_05,theme_prime.s_gallery_05,theme_prime.s_gallery_05,/theme_prime/static/src/img/snippets/s_gallery_05.jpg +s_gallery_06,theme_prime.s_gallery_06,theme_prime.s_gallery_06,/theme_prime/static/src/img/snippets/s_gallery_06.jpg +s_gallery_07,theme_prime.s_gallery_07,theme_prime.s_gallery_07,/theme_prime/static/src/img/snippets/s_gallery_07.jpg +s_gallery_08,theme_prime.s_gallery_08,theme_prime.s_gallery_08,/theme_prime/static/src/img/snippets/s_gallery_08.jpg +s_gallery_09,theme_prime.s_gallery_09,theme_prime.s_gallery_09,/theme_prime/static/src/img/snippets/s_gallery_09.jpg +s_gallery_10,theme_prime.s_gallery_10,theme_prime.s_gallery_10,/theme_prime/static/src/img/snippets/s_gallery_10.jpg +s_gallery_11,theme_prime.s_gallery_11,theme_prime.s_gallery_11,/theme_prime/static/src/img/snippets/s_gallery_11.jpg +s_gallery_12,theme_prime.s_gallery_12,theme_prime.s_gallery_12,/theme_prime/static/src/img/snippets/s_gallery_12.jpg +s_mega_menu_prime_2_1,theme_prime.s_mega_menu_prime_2_1,theme_prime.s_mega_menu_prime_2_1,/theme_prime/static/src/img/snippets/s_mega_menu_prime_2_1.jpg +s_mega_menu_prime_3_1,theme_prime.s_mega_menu_prime_3_1,theme_prime.s_mega_menu_prime_3_1,/theme_prime/static/src/img/snippets/s_mega_menu_prime_3_1.png +s_mega_menu_prime_4_1,theme_prime.s_mega_menu_prime_4_1,theme_prime.s_mega_menu_prime_4_1,/theme_prime/static/src/img/snippets/s_mega_menu_prime_4_1.jpg +s_mega_menu_prime_5_1,theme_prime.s_mega_menu_prime_5_1,theme_prime.s_mega_menu_prime_5_1,/theme_prime/static/src/img/snippets/s_mega_menu_prime_5_1.jpg +s_mega_menu_prime_5_2,theme_prime.s_mega_menu_prime_5_2,theme_prime.s_mega_menu_prime_5_2,/theme_prime/static/src/img/snippets/s_mega_menu_prime_5_2.jpg +s_mega_menu_prime_5_3,theme_prime.s_mega_menu_prime_5_3,theme_prime.s_mega_menu_prime_5_3,/theme_prime/static/src/img/snippets/s_mega_menu_prime_5_3.jpg +s_mega_menu_prime_5_4,theme_prime.s_mega_menu_prime_5_4,theme_prime.s_mega_menu_prime_5_4,/theme_prime/static/src/img/snippets/s_mega_menu_prime_5_4.jpg +s_mega_menu_prime_6_1,theme_prime.s_mega_menu_prime_6_1,theme_prime.s_mega_menu_prime_6_1,/theme_prime/static/src/img/snippets/s_mega_menu_prime_6_1.jpg +s_mega_menu_prime_6_2,theme_prime.s_mega_menu_prime_6_2,theme_prime.s_mega_menu_prime_6_2,/theme_prime/static/src/img/snippets/s_mega_menu_prime_6_2.png +s_mega_menu_prime_6_3,theme_prime.s_mega_menu_prime_6_3,theme_prime.s_mega_menu_prime_6_3,/theme_prime/static/src/img/snippets/s_mega_menu_prime_6_3.jpg +s_pricing_01,theme_prime.s_pricing_01,theme_prime.s_pricing_01,/theme_prime/static/src/img/snippets/s_pricing_01.jpg +s_pricing_02,theme_prime.s_pricing_02,theme_prime.s_pricing_02,/theme_prime/static/src/img/snippets/s_pricing_02.jpg +s_pricing_03,theme_prime.s_pricing_03,theme_prime.s_pricing_03,/theme_prime/static/src/img/snippets/s_pricing_03.jpg +s_pricing_04,theme_prime.s_pricing_04,theme_prime.s_pricing_04,/theme_prime/static/src/img/snippets/s_pricing_04.jpg +s_shop_offer_1_1,theme_prime.s_shop_offer_1_1,theme_prime.s_shop_offer_1_1,/theme_prime/static/src/img/snippets/s_shop_offer_1_1.jpg +s_shop_offer_3_1,theme_prime.s_shop_offer_3_1,theme_prime.s_shop_offer_3_1,/theme_prime/static/src/img/snippets/s_shop_offer_3_1.jpg +s_shop_offer_4_1,theme_prime.s_shop_offer_4_1,theme_prime.s_shop_offer_4_1,/theme_prime/static/src/img/snippets/s_shop_offer_4_1.jpg +s_subscribe_4_1,theme_prime.s_subscribe_4_1,theme_prime.s_subscribe_4_1,/theme_prime/static/src/img/snippets/s_subscribe_4_1.jpg +s_team_01,theme_prime.s_team_01,theme_prime.s_team_01,/theme_prime/static/src/img/snippets/s_team_01.png +s_team_02,theme_prime.s_team_02,theme_prime.s_team_02,/theme_prime/static/src/img/snippets/s_team_02.png +s_team_03,theme_prime.s_team_03,theme_prime.s_team_03,/theme_prime/static/src/img/snippets/s_team_03.png +s_team_04,theme_prime.s_team_04,theme_prime.s_team_04,/theme_prime/static/src/img/snippets/s_team_04.png +s_team_square_01,theme_prime.s_team_square_01,theme_prime.s_team_square_01,/theme_prime/static/src/img/snippets/s_team_square_01.png +s_team_square_02,theme_prime.s_team_square_02,theme_prime.s_team_square_02,/theme_prime/static/src/img/snippets/s_team_square_02.png +s_team_square_03,theme_prime.s_team_square_03,theme_prime.s_team_square_03,/theme_prime/static/src/img/snippets/s_team_square_03.png +s_team_square_04,theme_prime.s_team_square_04,theme_prime.s_team_square_04,/theme_prime/static/src/img/snippets/s_team_square_04.png +s_testimonial_author_01,theme_prime.s_testimonial_author_01,theme_prime.s_testimonial_author_01,/theme_prime/static/src/img/snippets/s_testimonial_author_01.jpg +s_testimonial_author_02,theme_prime.s_testimonial_author_02,theme_prime.s_testimonial_author_02,/theme_prime/static/src/img/snippets/s_testimonial_author_02.jpg +s_testimonial_author_03,theme_prime.s_testimonial_author_03,theme_prime.s_testimonial_author_03,/theme_prime/static/src/img/snippets/s_testimonial_author_03.jpg \ No newline at end of file diff --git a/theme_prime/doc/README.txt b/theme_prime/doc/README.txt new file mode 100644 index 000000000..3572943e2 --- /dev/null +++ b/theme_prime/doc/README.txt @@ -0,0 +1 @@ +The documentation is available at https://prime-docs-v17-24052022.droggol.com/ \ No newline at end of file diff --git a/theme_prime/models/__init__.py b/theme_prime/models/__init__.py new file mode 100644 index 000000000..53de46f3a --- /dev/null +++ b/theme_prime/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +from . import theme_prime +from . import ir_http +from . import product_template diff --git a/theme_prime/models/ir_http.py b/theme_prime/models/ir_http.py new file mode 100644 index 000000000..900069fe1 --- /dev/null +++ b/theme_prime/models/ir_http.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +from odoo import models + + +class IrHttp(models.AbstractModel): + _inherit = 'ir.http' + + @classmethod + def _get_translation_frontend_modules_name(cls): + mods = super(IrHttp, cls)._get_translation_frontend_modules_name() + return mods + ['droggol_theme_common', 'theme_prime'] diff --git a/theme_prime/models/product_template.py b/theme_prime/models/product_template.py new file mode 100644 index 000000000..cd8363929 --- /dev/null +++ b/theme_prime/models/product_template.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +from odoo.osv import expression +from odoo import models, fields, api, tools + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + dr_has_discount = fields.Boolean(compute="_compute_dr_has_discount", search="_search_dr_has_discount") + + def _search_dr_has_discount(self, operator, value): + pricelist_id = self._context.get('pricelist') + if pricelist_id: + discounted_product_ids = self._get_product_pricelist_data(pricelist_id) + operator = 'in' if operator == '!=' else 'not in' + return [('id', operator, discounted_product_ids)] + return [] + + def _need_catch_update(self, pricelist_id, catch_date): + catch_date_obj = fields.Datetime.to_datetime(catch_date) + item_ids_catch = self._pricelist_items_for_date(pricelist_id, catch_date) + item_ids_now = self._pricelist_items_for_date(pricelist_id, fields.Datetime.to_string(fields.Datetime.now())) + if set(item_ids_catch) != set(item_ids_now): + return True + + if self.env['product.pricelist'].browse(pricelist_id).write_date > catch_date_obj: + return True + + product_grouped_data = self.env['product.template'].read_group([('sale_ok', '=', True)], ['write_date:max'], ['sale_ok']) + if product_grouped_data: + product_date = product_grouped_data[0].get('write_date') + if product_date > catch_date_obj: + return True + return False + + @api.model + def _pricelist_items_for_date(self, pricelist_id, date): + self.env.cr.execute( + """ SELECT item.id FROM product_pricelist_item AS item + WHERE (item.pricelist_id = %s) AND (item.date_start IS NULL OR item.date_start<=%s) AND (item.date_end IS NULL OR item.date_end>=%s) + """, (pricelist_id, date, date)) + return [x[0] for x in self.env.cr.fetchall()] + + def _get_product_pricelist_data(self, pricelist_id): + discounted_product_ids, catch_date = self._get_product_pricelist_cache(pricelist_id) + need_catch_update = self._need_catch_update(pricelist_id, catch_date) + if need_catch_update: + self.clear_caches() + discounted_product_ids, catch_date = self._get_product_pricelist_cache(pricelist_id) + return discounted_product_ids + + @tools.ormcache('pricelist_id') + def _get_product_pricelist_cache(self, pricelist_id): + products = self.sudo().search([('sale_ok', '=', True), ('website_published', '=', True)]) # Need sudo so all products are calculated + pricelist = self.env['product.pricelist'].browse(pricelist_id) + discounted_product_ids = [] + website = self.env['website'].get_current_website() + # TODO: check fiscal_position_sudo PGA + fiscal_position_sudo = website.fiscal_position_id.sudo() + all_products_prices = products._get_sales_prices(pricelist, fiscal_position_sudo) + for p_id, price_data in all_products_prices.items(): + if price_data.get('base_price'): + discounted_product_ids.append(p_id) + return discounted_product_ids, fields.Datetime.to_string(fields.Datetime.now()) + + def _dr_process_product_data(self, product_pricelist_data, product): + return {'display_name': product_pricelist_data['display_name'], 'price': product_pricelist_data['price'], 'id': product_pricelist_data['product_template_id']} + + def _compute_dr_has_discount(self): + for product in self: + product.dr_has_discount = False + + def _get_product_category_count(self, domain=[]): + tables, where_clause, where_clause_params = self._dr_prepare_query_parts(domain) + query = """ + SELECT + count(product_template.id), + product_public_category.parent_path as path, + product_public_category.parent_id as parent_id, + product_public_category.id as product_public_category_id + FROM product_public_category_product_template_rel + JOIN product_template ON product_template.id = product_public_category_product_template_rel.product_template_id + """ + tables + """ + JOIN product_public_category ON product_public_category.id = product_public_category_product_template_rel.product_public_category_id + WHERE """ + where_clause + """ + GROUP BY product_public_category.id; + """ + + self.env.cr.execute(query, where_clause_params) + query_res = self.env.cr.dictfetchall() + + website = self.env['website'].get_current_website() + all_categ = self.env['product.public.category'].search(website.website_domain()) + all_categ_data = [{'path': pc.parent_path, 'parent_id': pc.parent_id.id, 'product_public_category_id': pc.id} for pc in all_categ] + + result_count = dict([(categ.id, 0) for categ in all_categ]) + + for line in all_categ_data: + for line2 in query_res: + if line.get('parent_id'): + path_pattern = '/%s/' % line.get('product_public_category_id') + if path_pattern in line2.get('path'): + result_count[line.get('product_public_category_id')] += line2.get('count') + else: + path_pattern = '%s/' % line.get('product_public_category_id') + if line2.get('path').startswith(path_pattern): + result_count[line.get('product_public_category_id')] += line2.get('count') + + return result_count + + def _get_product_attrib_count(self, attrib_values, domain=[]): + tables, where_clause, where_clause_params = self._dr_prepare_query_parts(domain) + + query = """ + SELECT + array_agg(product_template_attribute_line.product_tmpl_id) as product_tmpl_ids, + min(product_template_attribute_line.attribute_id) as product_attrib_id, + product_attribute_value.id + FROM product_template_attribute_line + JOIN product_attribute_value_product_template_attribute_line_rel + ON product_attribute_value_product_template_attribute_line_rel.product_template_attribute_line_id = product_template_attribute_line.id + JOIN product_attribute_value + ON product_attribute_value.id = product_attribute_value_product_template_attribute_line_rel.product_attribute_value_id + JOIN product_template ON product_template.id = product_template_attribute_line.product_tmpl_id + """ + tables + """ + WHERE """ + where_clause + """ or product_template.id is NULL + GROUP BY product_attribute_value.id + ORDER BY product_attrib_id; + """ + + self.env.cr.execute(query, where_clause_params) + query_res = self.env.cr.dictfetchall() + + result_count = {} + + if attrib_values: + + attrib_values_ids = [v[1] for v in attrib_values] + attrib_ids = [v[0] for v in attrib_values] + attrib_value_list = dict([(line.get('id'), line.get('product_tmpl_ids') or []) for line in query_res]) + + # Attribute -> Attribute Vals map + attrib_vals_map = {} + for line in query_res: + if not attrib_vals_map.get(line['product_attrib_id']): + attrib_vals_map[line['product_attrib_id']] = [] + attrib_vals_map[line['product_attrib_id']].append(line['id']) + + # Attribute -> active product list + attrib_p_list = {} + for line in query_res: + + value_id_1 = line.get('id') + product_ids_1 = line.get('product_tmpl_ids') or [] + attrib_id_1 = line.get('product_attrib_id') + + if not attrib_p_list.get(attrib_id_1): + attrib_p_list[attrib_id_1] = set() + + if value_id_1 in attrib_values_ids: + attrib_p_list[attrib_id_1] = attrib_p_list[attrib_id_1] | set(product_ids_1) + + # Attribute -> final list + attrib_product_list = {} + for line in query_res: + value_id_1 = line.get('id') + product_ids_1 = line.get('product_tmpl_ids') or [] + attrib_id_1 = line.get('product_attrib_id') + + if not attrib_product_list.get(value_id_1): + attrib_product_list[value_id_1] = set(product_ids_1) + + for line_2 in query_res: + value_id_2 = line_2.get('id') + product_ids_2 = line_2.get('product_tmpl_ids') or [] + attrib_id_2 = line_2.get('product_attrib_id') + + if value_id_2 not in attrib_vals_map.get(attrib_id_1, []) and value_id_2 in attrib_values_ids: + attrib_product_list[value_id_1] = attrib_product_list[value_id_1] & attrib_p_list.get(attrib_id_2, set()) + + result_count = dict([(val_id, len(product_ids)) for val_id, product_ids in attrib_product_list.items()]) + else: + result_count = dict([(line.get('id'), len(line.get('product_tmpl_ids') or [])) for line in query_res]) + return result_count + + def _get_product_rating_count(self, domain=[]): + tables, where_clause, where_clause_params = self._dr_prepare_query_parts(domain) + query = """ + SELECT avg_rating, count(*) FROM ( + SELECT FLOOR(avg(rating)) as avg_rating, rating_rating.res_id FROM rating_rating JOIN product_template ON product_template.id = rating_rating.res_id """ + tables + """ + WHERE (""" + where_clause + """) AND rating_rating.res_model = 'product.template' AND rating_rating.is_internal = False + GROUP BY rating_rating.res_id) AS rating_count group by avg_rating; """ + + self.env.cr.execute(query, where_clause_params) + query_result = dict(self.env.cr.fetchall()) + total = 0 + ratings_result = {rating: 0 for rating in range(1, 6)} + for result in range(5, 0, -1): + total += query_result.get(result, 0) + ratings_result[result] = total + return ratings_result + + def _dr_prepare_query_parts(self, domain): + query = self._where_calc(domain) + self._apply_ir_rules(query) + tables, where_clause, where_clause_params = query.get_sql() + tables = tables.replace('"product_template"', ' ', 1) + return tables, where_clause, where_clause_params + + def _search_render_results(self, fetch_fields, mapping, icon, limit): + current_website = self.env['website'].get_current_website() + if not current_website._dr_has_b2b_access(): + mapping.pop("detail", None) + return super()._search_render_results(fetch_fields, mapping, icon, limit) + + # Below block is to support fallback products + @api.model + def _search_fetch(self, search_detail, search, limit, order): + if search_detail.get('dr_search_domain'): + search_detail['base_domain'].append(search_detail.get('dr_search_domain')) + search = False + return super()._search_fetch(search_detail, search, limit, order) + + @api.model + def _search_get_detail(self, website, order, options): + result = super()._search_get_detail(website, order, options) + if options.get('dr_search_domain'): + result['dr_search_domain'] = options.get('dr_search_domain') + return result diff --git a/theme_prime/models/theme_prime.py b/theme_prime/models/theme_prime.py new file mode 100644 index 000000000..5f56a869f --- /dev/null +++ b/theme_prime/models/theme_prime.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019-Present Droggol Infotech Private Limited. () + +from odoo import api, models + + +class ThemePrime(models.AbstractModel): + _inherit = 'theme.utils' + + @api.model + def _reset_default_config(self): + header_styles = list(range(1, 9)) + for style in header_styles: + self.disable_view('theme_prime.template_header_style_%s' % style) + + footer_styles = list(range(1, 11)) + for style in footer_styles: + self.disable_view('theme_prime.template_footer_style_%s' % style) + + super()._reset_default_config() diff --git a/theme_prime/static/description/icon.png b/theme_prime/static/description/icon.png new file mode 100644 index 000000000..94cae5d06 Binary files /dev/null and b/theme_prime/static/description/icon.png differ diff --git a/theme_prime/static/description/images/Copy of Cart Flow Options.png b/theme_prime/static/description/images/Copy of Cart Flow Options.png new file mode 100644 index 000000000..9e37edd9d Binary files /dev/null and b/theme_prime/static/description/images/Copy of Cart Flow Options.png differ diff --git a/theme_prime/static/description/images/b2b-mode.png b/theme_prime/static/description/images/b2b-mode.png new file mode 100644 index 000000000..5f8d96078 Binary files /dev/null and b/theme_prime/static/description/images/b2b-mode.png differ diff --git a/theme_prime/static/description/images/card-features.gif b/theme_prime/static/description/images/card-features.gif new file mode 100644 index 000000000..f3d37556b Binary files /dev/null and b/theme_prime/static/description/images/card-features.gif differ diff --git a/theme_prime/static/description/images/cards.gif b/theme_prime/static/description/images/cards.gif new file mode 100644 index 000000000..9c9dea469 Binary files /dev/null and b/theme_prime/static/description/images/cards.gif differ diff --git a/theme_prime/static/description/images/cards.png b/theme_prime/static/description/images/cards.png new file mode 100644 index 000000000..c63ead2bb Binary files /dev/null and b/theme_prime/static/description/images/cards.png differ diff --git a/theme_prime/static/description/images/cart-1.png b/theme_prime/static/description/images/cart-1.png new file mode 100644 index 000000000..12cbedea6 Binary files /dev/null and b/theme_prime/static/description/images/cart-1.png differ diff --git a/theme_prime/static/description/images/cart-2.png b/theme_prime/static/description/images/cart-2.png new file mode 100644 index 000000000..b483a372c Binary files /dev/null and b/theme_prime/static/description/images/cart-2.png differ diff --git a/theme_prime/static/description/images/cart-icon.png b/theme_prime/static/description/images/cart-icon.png new file mode 100644 index 000000000..59029fc37 Binary files /dev/null and b/theme_prime/static/description/images/cart-icon.png differ diff --git a/theme_prime/static/description/images/config-footer.png b/theme_prime/static/description/images/config-footer.png new file mode 100644 index 000000000..be3842ab6 Binary files /dev/null and b/theme_prime/static/description/images/config-footer.png differ diff --git a/theme_prime/static/description/images/contact.png b/theme_prime/static/description/images/contact.png new file mode 100644 index 000000000..d912a8ac6 Binary files /dev/null and b/theme_prime/static/description/images/contact.png differ diff --git a/theme_prime/static/description/images/features/01-quick-view.png b/theme_prime/static/description/images/features/01-quick-view.png new file mode 100644 index 000000000..32f70a47e Binary files /dev/null and b/theme_prime/static/description/images/features/01-quick-view.png differ diff --git a/theme_prime/static/description/images/features/02-tabs.png b/theme_prime/static/description/images/features/02-tabs.png new file mode 100644 index 000000000..b1e147697 Binary files /dev/null and b/theme_prime/static/description/images/features/02-tabs.png differ diff --git a/theme_prime/static/description/images/features/03-pricelist.png b/theme_prime/static/description/images/features/03-pricelist.png new file mode 100644 index 000000000..c084c2e34 Binary files /dev/null and b/theme_prime/static/description/images/features/03-pricelist.png differ diff --git a/theme_prime/static/description/images/features/04-portal.png b/theme_prime/static/description/images/features/04-portal.png new file mode 100644 index 000000000..7065dcba5 Binary files /dev/null and b/theme_prime/static/description/images/features/04-portal.png differ diff --git a/theme_prime/static/description/images/features/05-offers-popup.png b/theme_prime/static/description/images/features/05-offers-popup.png new file mode 100644 index 000000000..50fccb1ae Binary files /dev/null and b/theme_prime/static/description/images/features/05-offers-popup.png differ diff --git a/theme_prime/static/description/images/features/06-attr-popup.png b/theme_prime/static/description/images/features/06-attr-popup.png new file mode 100644 index 000000000..32094cec4 Binary files /dev/null and b/theme_prime/static/description/images/features/06-attr-popup.png differ diff --git a/theme_prime/static/description/images/features/07-doc.png b/theme_prime/static/description/images/features/07-doc.png new file mode 100644 index 000000000..aa47248c8 Binary files /dev/null and b/theme_prime/static/description/images/features/07-doc.png differ diff --git a/theme_prime/static/description/images/features/08-doc.png b/theme_prime/static/description/images/features/08-doc.png new file mode 100644 index 000000000..cd6d3fe8b Binary files /dev/null and b/theme_prime/static/description/images/features/08-doc.png differ diff --git a/theme_prime/static/description/images/features/09-brand.png b/theme_prime/static/description/images/features/09-brand.png new file mode 100644 index 000000000..ddab23df1 Binary files /dev/null and b/theme_prime/static/description/images/features/09-brand.png differ diff --git a/theme_prime/static/description/images/features/10-countdown.png b/theme_prime/static/description/images/features/10-countdown.png new file mode 100644 index 000000000..abdd4a140 Binary files /dev/null and b/theme_prime/static/description/images/features/10-countdown.png differ diff --git a/theme_prime/static/description/images/features/11-icons.png b/theme_prime/static/description/images/features/11-icons.png new file mode 100644 index 000000000..eac7a0955 Binary files /dev/null and b/theme_prime/static/description/images/features/11-icons.png differ diff --git a/theme_prime/static/description/images/features/12-coming-soon.png b/theme_prime/static/description/images/features/12-coming-soon.png new file mode 100644 index 000000000..1c684dd40 Binary files /dev/null and b/theme_prime/static/description/images/features/12-coming-soon.png differ diff --git a/theme_prime/static/description/images/features/13-zoom.png b/theme_prime/static/description/images/features/13-zoom.png new file mode 100644 index 000000000..169cf519f Binary files /dev/null and b/theme_prime/static/description/images/features/13-zoom.png differ diff --git a/theme_prime/static/description/images/features/14-pricelist-timer.png b/theme_prime/static/description/images/features/14-pricelist-timer.png new file mode 100644 index 000000000..0752a956f Binary files /dev/null and b/theme_prime/static/description/images/features/14-pricelist-timer.png differ diff --git a/theme_prime/static/description/images/features/15-multi-website.png b/theme_prime/static/description/images/features/15-multi-website.png new file mode 100644 index 000000000..621ee5520 Binary files /dev/null and b/theme_prime/static/description/images/features/15-multi-website.png differ diff --git a/theme_prime/static/description/images/features/16-rtl.png b/theme_prime/static/description/images/features/16-rtl.png new file mode 100644 index 000000000..824a5f069 Binary files /dev/null and b/theme_prime/static/description/images/features/16-rtl.png differ diff --git a/theme_prime/static/description/images/features/17-highlited-menu.png b/theme_prime/static/description/images/features/17-highlited-menu.png new file mode 100644 index 000000000..ba86c5fe8 Binary files /dev/null and b/theme_prime/static/description/images/features/17-highlited-menu.png differ diff --git a/theme_prime/static/description/images/features/18-all-brands-page.png b/theme_prime/static/description/images/features/18-all-brands-page.png new file mode 100644 index 000000000..182b37f60 Binary files /dev/null and b/theme_prime/static/description/images/features/18-all-brands-page.png differ diff --git a/theme_prime/static/description/images/features/19-layload-products.png b/theme_prime/static/description/images/features/19-layload-products.png new file mode 100644 index 000000000..f9e785b11 Binary files /dev/null and b/theme_prime/static/description/images/features/19-layload-products.png differ diff --git a/theme_prime/static/description/images/features/20-product-tags.png b/theme_prime/static/description/images/features/20-product-tags.png new file mode 100644 index 000000000..7312692dd Binary files /dev/null and b/theme_prime/static/description/images/features/20-product-tags.png differ diff --git a/theme_prime/static/description/images/features/90-demo-1.png b/theme_prime/static/description/images/features/90-demo-1.png new file mode 100644 index 000000000..7b78a27c7 Binary files /dev/null and b/theme_prime/static/description/images/features/90-demo-1.png differ diff --git a/theme_prime/static/description/images/features/90-demo-2.png b/theme_prime/static/description/images/features/90-demo-2.png new file mode 100644 index 000000000..5b1151bb2 Binary files /dev/null and b/theme_prime/static/description/images/features/90-demo-2.png differ diff --git a/theme_prime/static/description/images/features/90-demo-3.png b/theme_prime/static/description/images/features/90-demo-3.png new file mode 100644 index 000000000..03bea86fe Binary files /dev/null and b/theme_prime/static/description/images/features/90-demo-3.png differ diff --git a/theme_prime/static/description/images/features/docs_screenshot.png b/theme_prime/static/description/images/features/docs_screenshot.png new file mode 100644 index 000000000..c3dda8f81 Binary files /dev/null and b/theme_prime/static/description/images/features/docs_screenshot.png differ diff --git a/theme_prime/static/description/images/filter-footer.png b/theme_prime/static/description/images/filter-footer.png new file mode 100644 index 000000000..8aff36ff2 Binary files /dev/null and b/theme_prime/static/description/images/filter-footer.png differ diff --git a/theme_prime/static/description/images/filter.gif b/theme_prime/static/description/images/filter.gif new file mode 100644 index 000000000..92dc6d006 Binary files /dev/null and b/theme_prime/static/description/images/filter.gif differ diff --git a/theme_prime/static/description/images/filters-hero.png b/theme_prime/static/description/images/filters-hero.png new file mode 100644 index 000000000..92ccb076d Binary files /dev/null and b/theme_prime/static/description/images/filters-hero.png differ diff --git a/theme_prime/static/description/images/header.gif b/theme_prime/static/description/images/header.gif new file mode 100644 index 000000000..60d474890 Binary files /dev/null and b/theme_prime/static/description/images/header.gif differ diff --git a/theme_prime/static/description/images/header.png b/theme_prime/static/description/images/header.png new file mode 100644 index 000000000..7e614f33d Binary files /dev/null and b/theme_prime/static/description/images/header.png differ diff --git a/theme_prime/static/description/images/headers-2.png b/theme_prime/static/description/images/headers-2.png new file mode 100644 index 000000000..01cbfbf75 Binary files /dev/null and b/theme_prime/static/description/images/headers-2.png differ diff --git a/theme_prime/static/description/images/hero-2.png b/theme_prime/static/description/images/hero-2.png new file mode 100644 index 000000000..c29ffd4d5 Binary files /dev/null and b/theme_prime/static/description/images/hero-2.png differ diff --git a/theme_prime/static/description/images/hero.png b/theme_prime/static/description/images/hero.png new file mode 100644 index 000000000..a3c0f968f Binary files /dev/null and b/theme_prime/static/description/images/hero.png differ diff --git a/theme_prime/static/description/images/hero.webp b/theme_prime/static/description/images/hero.webp new file mode 100644 index 000000000..4907a2bdb Binary files /dev/null and b/theme_prime/static/description/images/hero.webp differ diff --git a/theme_prime/static/description/images/hotspot-1.gif b/theme_prime/static/description/images/hotspot-1.gif new file mode 100644 index 000000000..7678dc450 Binary files /dev/null and b/theme_prime/static/description/images/hotspot-1.gif differ diff --git a/theme_prime/static/description/images/hotspot-3.png b/theme_prime/static/description/images/hotspot-3.png new file mode 100644 index 000000000..4454e2564 Binary files /dev/null and b/theme_prime/static/description/images/hotspot-3.png differ diff --git a/theme_prime/static/description/images/hotspot-4.gif b/theme_prime/static/description/images/hotspot-4.gif new file mode 100644 index 000000000..1981e282f Binary files /dev/null and b/theme_prime/static/description/images/hotspot-4.gif differ diff --git a/theme_prime/static/description/images/hotspot.png b/theme_prime/static/description/images/hotspot.png new file mode 100644 index 000000000..11e302fcf Binary files /dev/null and b/theme_prime/static/description/images/hotspot.png differ diff --git a/theme_prime/static/description/images/megamenu-footer.png b/theme_prime/static/description/images/megamenu-footer.png new file mode 100644 index 000000000..cf03d71ec Binary files /dev/null and b/theme_prime/static/description/images/megamenu-footer.png differ diff --git a/theme_prime/static/description/images/megamenu-hero-2.png b/theme_prime/static/description/images/megamenu-hero-2.png new file mode 100644 index 000000000..8224dd95f Binary files /dev/null and b/theme_prime/static/description/images/megamenu-hero-2.png differ diff --git a/theme_prime/static/description/images/megamenu-hero.png b/theme_prime/static/description/images/megamenu-hero.png new file mode 100644 index 000000000..749d6949c Binary files /dev/null and b/theme_prime/static/description/images/megamenu-hero.png differ diff --git a/theme_prime/static/description/images/megamenu.gif b/theme_prime/static/description/images/megamenu.gif new file mode 100644 index 000000000..22d621412 Binary files /dev/null and b/theme_prime/static/description/images/megamenu.gif differ diff --git a/theme_prime/static/description/images/megamenu.mp4 b/theme_prime/static/description/images/megamenu.mp4 new file mode 100644 index 000000000..507bdbd93 Binary files /dev/null and b/theme_prime/static/description/images/megamenu.mp4 differ diff --git a/theme_prime/static/description/images/menus.png b/theme_prime/static/description/images/menus.png new file mode 100644 index 000000000..51b16da30 Binary files /dev/null and b/theme_prime/static/description/images/menus.png differ diff --git a/theme_prime/static/description/images/mobile.mp4 b/theme_prime/static/description/images/mobile.mp4 new file mode 100644 index 000000000..ad44bd116 Binary files /dev/null and b/theme_prime/static/description/images/mobile.mp4 differ diff --git a/theme_prime/static/description/images/mobile.png b/theme_prime/static/description/images/mobile.png new file mode 100644 index 000000000..12bf837bc Binary files /dev/null and b/theme_prime/static/description/images/mobile.png differ diff --git a/theme_prime/static/description/images/prime-logo.png b/theme_prime/static/description/images/prime-logo.png new file mode 100644 index 000000000..468f99b5d Binary files /dev/null and b/theme_prime/static/description/images/prime-logo.png differ diff --git a/theme_prime/static/description/images/pwa.png b/theme_prime/static/description/images/pwa.png new file mode 100644 index 000000000..b267933e8 Binary files /dev/null and b/theme_prime/static/description/images/pwa.png differ diff --git a/theme_prime/static/description/images/search-ss.gif b/theme_prime/static/description/images/search-ss.gif new file mode 100644 index 000000000..a55c856f7 Binary files /dev/null and b/theme_prime/static/description/images/search-ss.gif differ diff --git a/theme_prime/static/description/images/search.gif b/theme_prime/static/description/images/search.gif new file mode 100644 index 000000000..dfea44701 Binary files /dev/null and b/theme_prime/static/description/images/search.gif differ diff --git a/theme_prime/static/description/images/searchss.mp4 b/theme_prime/static/description/images/searchss.mp4 new file mode 100644 index 000000000..8d647bb6e Binary files /dev/null and b/theme_prime/static/description/images/searchss.mp4 differ diff --git a/theme_prime/static/description/images/settings.png b/theme_prime/static/description/images/settings.png new file mode 100644 index 000000000..aa7f63257 Binary files /dev/null and b/theme_prime/static/description/images/settings.png differ diff --git a/theme_prime/static/description/images/smart-search.png b/theme_prime/static/description/images/smart-search.png new file mode 100644 index 000000000..1df6e181a Binary files /dev/null and b/theme_prime/static/description/images/smart-search.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/1.png b/theme_prime/static/description/images/snippet_thumb/1.png new file mode 100644 index 000000000..7d62fa395 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/1.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/10.png b/theme_prime/static/description/images/snippet_thumb/10.png new file mode 100644 index 000000000..fe3fd84cd Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/10.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/11.png b/theme_prime/static/description/images/snippet_thumb/11.png new file mode 100644 index 000000000..ef9362bac Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/11.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/12.png b/theme_prime/static/description/images/snippet_thumb/12.png new file mode 100644 index 000000000..7af6a012c Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/12.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/13.png b/theme_prime/static/description/images/snippet_thumb/13.png new file mode 100644 index 000000000..d68143c06 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/13.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/14.png b/theme_prime/static/description/images/snippet_thumb/14.png new file mode 100644 index 000000000..510585a2a Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/14.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/15.png b/theme_prime/static/description/images/snippet_thumb/15.png new file mode 100644 index 000000000..71cb55dd2 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/15.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/16.png b/theme_prime/static/description/images/snippet_thumb/16.png new file mode 100644 index 000000000..bf3634704 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/16.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/17.png b/theme_prime/static/description/images/snippet_thumb/17.png new file mode 100644 index 000000000..4779e3b07 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/17.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/2.png b/theme_prime/static/description/images/snippet_thumb/2.png new file mode 100644 index 000000000..34f997722 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/2.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/3.png b/theme_prime/static/description/images/snippet_thumb/3.png new file mode 100644 index 000000000..b1902cc55 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/3.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/4.png b/theme_prime/static/description/images/snippet_thumb/4.png new file mode 100644 index 000000000..f44c4d594 Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/4.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/5.png b/theme_prime/static/description/images/snippet_thumb/5.png new file mode 100644 index 000000000..ea195fb1a Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/5.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/6.png b/theme_prime/static/description/images/snippet_thumb/6.png new file mode 100644 index 000000000..98aaaa82a Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/6.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/7.png b/theme_prime/static/description/images/snippet_thumb/7.png new file mode 100644 index 000000000..9054033df Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/7.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/8.png b/theme_prime/static/description/images/snippet_thumb/8.png new file mode 100644 index 000000000..7b399b6ad Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/8.png differ diff --git a/theme_prime/static/description/images/snippet_thumb/9.png b/theme_prime/static/description/images/snippet_thumb/9.png new file mode 100644 index 000000000..eb09f195e Binary files /dev/null and b/theme_prime/static/description/images/snippet_thumb/9.png differ diff --git a/theme_prime/static/description/images/theme-configurator-old.png b/theme_prime/static/description/images/theme-configurator-old.png new file mode 100644 index 000000000..32046b869 Binary files /dev/null and b/theme_prime/static/description/images/theme-configurator-old.png differ diff --git a/theme_prime/static/description/images/theme-configurator.png b/theme_prime/static/description/images/theme-configurator.png new file mode 100644 index 000000000..bc42395fb Binary files /dev/null and b/theme_prime/static/description/images/theme-configurator.png differ diff --git a/theme_prime/static/description/images/tools-text.png b/theme_prime/static/description/images/tools-text.png new file mode 100644 index 000000000..3977b342d Binary files /dev/null and b/theme_prime/static/description/images/tools-text.png differ diff --git a/theme_prime/static/description/index.html b/theme_prime/static/description/index.html new file mode 100644 index 000000000..4907582e6 --- /dev/null +++ b/theme_prime/static/description/index.html @@ -0,0 +1,754 @@ +
+
+
+
+
+ + +
+
+
+ +
+ + Need a help? + +
+ info@droggol.com +
+
+ +
+
+
+ + +
+ + +
+ +
+
+
+ +
+
+
+
+ + + + +
+
+
+ +
+ +
+ +
+

Answer your visitors questions instantly.
Messages pop up on your backend.

+
+
+

Create customized forms to convert more, and integrate them in your business flow in just a few clicks.

+
+
+

Let people review and rate your products. Showcase your best products thanks to customers' comments.

+
+
+

Boost your sales with cross-selling and upselling opportunities on product pages, in the cart, or at checkout.

+
+
+

Encourage potential customers to buy your products with coupons and promotion campaigns.

+
+ + +
+ + + +
+
+ + +
+
+
+ +
+ + + +
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ + + + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+
+
+
+

Explore Live Demo
for Electronic Store

+
+
+ Copy Given URL for Demo
+
+

+ droggol.com/r/demo17-1

+
+
+
+
+
+
+
+
+ + +
+
+
+ +
+
+
+
+

Why theme prime

+

+ There several reasons to choose theme prime. One of them is, its wide range of + configuration options. + With tons of configuration option you can mold your eCommerce based on your choice. + These configuration options are so easy to use that even user with no technical + knowledge can configure the eCommerce. +

+ Theme prime has all the options that are required to run any modern eCommerce theme. It + supports multi-language and multi-website + so you can run multiple website with different configuration in the same odoo instance. +

+

+ Theme prime is developed by experience developers so it has top notch quality. Even if + it has tones of feature it does not slow down and runs without any hassle. +

+
+
+
+
+
+
+

Full Documentation

+

We would like explain each and every + feature of our theme that's why we have created + detailed documentation. + We are constantly improving and adding new content and videos. +

+
+
+ +
+
+ A full documentation + can be accessed once you purchase the theme. It is available in /docs folder of + theme module. +
+
+
+
+ +
+
+
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ +
+
+

Changelog

+
+

+ v17.0.0.0-Dec 26, 2023 +

+
    +
  • + Initial release for v17 +
  • +
+
+
+ +
+ diff --git a/theme_prime/static/description/other_apps/app_1.png b/theme_prime/static/description/other_apps/app_1.png new file mode 100644 index 000000000..0f5c5e30f Binary files /dev/null and b/theme_prime/static/description/other_apps/app_1.png differ diff --git a/theme_prime/static/description/other_apps/app_2.png b/theme_prime/static/description/other_apps/app_2.png new file mode 100644 index 000000000..9a7e19641 Binary files /dev/null and b/theme_prime/static/description/other_apps/app_2.png differ diff --git a/theme_prime/static/description/other_apps/app_3.png b/theme_prime/static/description/other_apps/app_3.png new file mode 100644 index 000000000..e4fc1e8a1 Binary files /dev/null and b/theme_prime/static/description/other_apps/app_3.png differ diff --git a/theme_prime/static/description/prime_cover.png b/theme_prime/static/description/prime_cover.png new file mode 100644 index 000000000..cdffa589a Binary files /dev/null and b/theme_prime/static/description/prime_cover.png differ diff --git a/theme_prime/static/description/prime_screenshot.gif b/theme_prime/static/description/prime_screenshot.gif new file mode 100644 index 000000000..b3cab9d40 Binary files /dev/null and b/theme_prime/static/description/prime_screenshot.gif differ diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-1.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-1.css new file mode 100644 index 000000000..5f5d78f15 --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-1.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-eye:before { + content: "\e905"; +} +.dri-wishlist:before { + content: "\e906"; +} +.dri-compare:before { + content: "\e900"; +} +.dri-search:before { + content: "\e901"; +} +.dri-cart:before { + content: "\e902"; +} +.dri-user:before { + content: "\e903"; +} +.dri-bolt:before { + content: "\e904"; +} +.dri-home-l:before { + content: "\e800"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-2.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-2.css new file mode 100644 index 000000000..76234b22b --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-2.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-home-l:before { + content: "\e800"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search:before { + content: "\f002"; +} +.dri-wishlist:before { + content: "\f004"; +} +.dri-user:before { + content: "\f007"; +} +.dri-eye:before { + content: "\f06e"; +} +.dri-cart:before { + content: "\f07a"; +} +.dri-bolt:before { + content: "\f0e7"; +} +.dri-compare:before { + content: "\f0ec"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-3.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-3.css new file mode 100644 index 000000000..7ebbb0ba2 --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-3.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-eye:before { + content: "\ecb5"; +} +.dri-bolt:before { + content: "\ed3d"; +} +.dri-wishlist:before { + content: "\ee0b"; +} +.dri-compare:before { + content: "\f074"; +} +.dri-search:before { + content: "\f0d1"; +} +.dri-cart:before { + content: "\f120"; +} +.dri-user:before { + content: "\f264"; +} +.dri-home-l:before { + content: "\e800"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-4.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-4.css new file mode 100644 index 000000000..e054f932b --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-4.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-bolt:before { + content: "\e913"; +} +.dri-wishlist:before { + content: "\e914"; +} +.dri-compare:before { + content: "\e915"; +} +.dri-search:before { + content: "\e916"; +} +.dri-cart:before { + content: "\e917"; +} +.dri-user:before { + content: "\e91b"; +} +.dri-eye:before { + content: "\e918"; +} +.dri-home-l:before { + content: "\e800"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-5.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-5.css new file mode 100644 index 000000000..81b372c7e --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-5.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-eye:before { + content: "\e90d"; +} +.dri-bolt:before { + content: "\e90e"; +} +.dri-wishlist:before { + content: "\e90f"; +} +.dri-search:before { + content: "\e911"; +} +.dri-cart:before { + content: "\e912"; +} +.dri-compare:before { + content: "\e910"; +} +.dri-user:before { + content: "\e91a"; +} +.dri-home-l:before { + content: "\e800"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-6.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-6.css new file mode 100644 index 000000000..c0f469be8 --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-6.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-eye:before { + content: "\e907"; +} +.dri-cart:before { + content: "\e908"; +} +.dri-user:before { + content: "\e919"; +} +.dri-wishlist:before { + content: "\e909"; +} +.dri-search:before { + content: "\e90a"; +} +.dri-compare:before { + content: "\e90b"; +} +.dri-bolt:before { + content: "\e90c"; +} +.dri-home-l:before { + content: "\e800"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-7.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-7.css new file mode 100644 index 000000000..b371d98ef --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-7.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-search:before { + content: "\e91c"; +} +.dri-user:before { + content: "\e91d"; +} +.dri-wishlist:before { + content: "\e91e"; +} +.dri-cart:before { + content: "\e91f"; +} +.dri-eye:before { + content: "\e920"; +} +.dri-bolt:before { + content: "\e921"; +} +.dri-compare:before { + content: "\e922"; +} +.dri-home-l:before { + content: "\e800"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-8.css b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-8.css new file mode 100644 index 000000000..4d9b09f46 --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/droggol-pack-8.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'Droggol-Pack'; + src: url('fonts/Droggol-Pack.eot?4iwoe7'); + src: url('fonts/Droggol-Pack.eot?4iwoe7#iefix') format('embedded-opentype'), + url('fonts/Droggol-Pack.ttf?4iwoe7') format('truetype'), + url('fonts/Droggol-Pack.woff?4iwoe7') format('woff'), + url('fonts/Droggol-Pack.svg?4iwoe7#Droggol-Pack') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +.dri { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'Droggol-Pack' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dri-home-l:before { + content: "\e800"; +} +.dri-bolt:before { + content: "\e803"; +} +.dri-wishlist:before { + content: "\e813"; +} +.dri-eye:before { + content: "\e81b"; +} +.dri-user:before { + content: "\e82a"; +} +.dri-cart:before { + content: "\e82e"; +} +.dri-tag-l:before { + content: "\e82f"; +} +.dri-phone-l:before { + content: "\e830"; +} +.dri-rocket-l:before { + content: "\e84b"; +} +.dri-compare:before { + content: "\e862"; +} +.dri-search:before { + content: "\e86f"; +} +.dri-cross-l:before { + content: "\e870"; +} +.dri-chevron-up-l:before { + content: "\e873"; +} +.dri-chevron-down-l:before { + content: "\e874"; +} +.dri-chevron-left-l:before { + content: "\e875"; +} +.dri-chevron-right-l:before { + content: "\e876"; +} +.dri-arrow-left-l:before { + content: "\e879"; +} +.dri-arrow-right-l:before { + content: "\e87a"; +} +.dri-search-l:before { + content: "\e86f"; +} +.dri-sync-l:before { + content: "\e862"; +} +.dri-menu-circle-l:before { + content: "\e87e"; +} +.dri-category:before { + content: "\e923"; +} diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.eot b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.eot new file mode 100644 index 000000000..4e30985a5 Binary files /dev/null and b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.eot differ diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.svg b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.svg new file mode 100644 index 000000000..dabb19548 --- /dev/null +++ b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.svg @@ -0,0 +1,101 @@ + + + + + + +{ + "fontFamily": "Droggol-Pack", + "fontURL": "https://droggol.com/", + "designerURL": "https://droggol.com/", + "license": "", + "licenseURL": "", + "description": "Font generated by IcoMoon.", + "copyright": "Droggol", + "majorVersion": 1, + "minorVersion": 0, + "designer": "https://droggol.com/", + "version": "Version 1.0", + "fontId": "Droggol-Pack", + "psName": "Droggol-Pack", + "subFamily": "Regular", + "fullName": "Droggol-Pack" +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.ttf b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.ttf new file mode 100644 index 000000000..0d0d192ae Binary files /dev/null and b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.ttf differ diff --git a/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.woff b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.woff new file mode 100644 index 000000000..bbfe0bef4 Binary files /dev/null and b/theme_prime/static/lib/Droggol-Pack-v1.0/fonts/Droggol-Pack.woff differ diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/LICENSE b/theme_prime/static/lib/OwlCarousel2-2.3.4/LICENSE new file mode 100644 index 000000000..052211da5 --- /dev/null +++ b/theme_prime/static/lib/OwlCarousel2-2.3.4/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2014 Owl +Modified work Copyright 2016-2018 David Deutsch + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/README.md b/theme_prime/static/lib/OwlCarousel2-2.3.4/README.md new file mode 100644 index 000000000..e912df2d5 --- /dev/null +++ b/theme_prime/static/lib/OwlCarousel2-2.3.4/README.md @@ -0,0 +1,122 @@ +# Owl Carousel 2 + +Touch enabled [jQuery](https://jquery.com/) plugin that lets you create a beautiful, responsive carousel slider. **To get started, check out https://owlcarousel2.github.io/OwlCarousel2/.** + +**Notice:** The old Owl Carousel site (owlgraphic [dot] com) is no longer in use. Please delete all references to this in bookmarks and your own products' documentation as it's being used for malicious purposes. + +## Quick start + +### Install + +This package can be installed with: + +- [npm](https://www.npmjs.com/package/owl.carousel): `npm install --save owl.carousel` or `yarn add owl.carousel jquery` +- [bower](http://bower.io/search/?q=owl.carousel): `bower install --save owl.carousel` + +Or download the [latest release](https://github.com/OwlCarousel2/OwlCarousel2/releases). + +### Load + +#### Webpack + +Add jQuery via the "webpack.ProvidePlugin" to your webpack configuration: + + const webpack = require('webpack'); + + //... + plugins: [ + new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + 'window.jQuery': 'jquery' + }), + ], + //... + +Load the required stylesheet and JS: + +```js +import 'owl.carousel/dist/assets/owl.carousel.css'; +import 'owl.carousel'; +``` + +#### Static HTML + +Put the required stylesheet at the [top](https://developer.yahoo.com/performance/rules.html#css_top) of your markup: + +```html + +``` + +```html + +``` + +**NOTE:** If you want to use the default navigation styles, you will also need to include `owl.theme.default.css`. + + +Put the script at the [bottom](https://developer.yahoo.com/performance/rules.html#js_bottom) of your markup right after jQuery: + +```html + + +``` + +```html + + +``` + +### Usage + +Wrap your items (`div`, `a`, `img`, `span`, `li` etc.) with a container element (`div`, `ul` etc.). Only the class `owl-carousel` is mandatory to apply proper styles: + +```html + +``` +**NOTE:** The `owl-theme` class is optional, but without it, you will need to style navigation features on your own. + + +Call the [plugin](https://learn.jquery.com/plugins/) function and your carousel is ready. + +```javascript +$(document).ready(function(){ + $('.owl-carousel').owlCarousel(); +}); +``` + +## Documentation + +The documentation, included in this repo in the root directory, is built with [Assemble](http://assemble.io/) and publicly available at https://owlcarousel2.github.io/OwlCarousel2/. The documentation may also be run locally. + +## Building + +This package comes with [Grunt](http://gruntjs.com/) and [Bower](http://bower.io/). The following tasks are available: + + * `default` compiles the CSS and JS into `/dist` and builds the doc. + * `dist` compiles the CSS and JS into `/dist` only. + * `watch` watches source files and builds them automatically whenever you save. + * `test` runs [JSHint](http://www.jshint.com/) and [QUnit](http://qunitjs.com/) tests headlessly in [PhantomJS](http://phantomjs.org/). + +To define which plugins are build into the distribution just edit `/_config.json` to fit your needs. + +## Contributing + +Please read [CONTRIBUTING.md](CONTRIBUTING.md). + +## Roadmap + +Please make sure to check out our [Roadmap Discussion](https://github.com/OwlCarousel2/OwlCarousel2/issues/1756). + + +## License + +The code and the documentation are released under the [MIT License](LICENSE). diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/ajax-loader.gif b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/ajax-loader.gif new file mode 100644 index 000000000..d3962f969 Binary files /dev/null and b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/ajax-loader.gif differ diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.carousel.css b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.carousel.css new file mode 100644 index 000000000..40237bc65 --- /dev/null +++ b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.carousel.css @@ -0,0 +1,186 @@ +/** + * Owl Carousel v2.3.4 + * Copyright 2013-2018 David Deutsch + * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE + */ +/* + * Owl Carousel - Core + */ +.owl-carousel { + display: none; + width: 100%; + -webkit-tap-highlight-color: transparent; + /* position relative and z-index fix webkit rendering fonts issue */ + position: relative; + z-index: 1; } + .owl-carousel .owl-stage { + position: relative; + -ms-touch-action: pan-Y; + touch-action: manipulation; + -moz-backface-visibility: hidden; + /* fix firefox animation glitch */ } + .owl-carousel .owl-stage:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; } + .owl-carousel .owl-stage-outer { + position: relative; + overflow: hidden; + /* fix for flashing background */ + -webkit-transform: translate3d(0px, 0px, 0px); } + .owl-carousel .owl-wrapper, + .owl-carousel .owl-item { + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -ms-backface-visibility: hidden; + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); } + .owl-carousel .owl-item { + position: relative; + min-height: 1px; + float: left; + -webkit-backface-visibility: hidden; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; } + .owl-carousel .owl-item img { + display: block; + width: 100%; } + .owl-carousel .owl-nav.disabled, + .owl-carousel .owl-dots.disabled { + display: none; } + .owl-carousel .owl-nav .owl-prev, + .owl-carousel .owl-nav .owl-next, + .owl-carousel .owl-dot { + cursor: pointer; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .owl-carousel .owl-nav button.owl-prev, + .owl-carousel .owl-nav button.owl-next, + .owl-carousel button.owl-dot { + background: none; + color: inherit; + border: none; + padding: 0 !important; + font: inherit; } + .owl-carousel.owl-loaded { + display: block; } + .owl-carousel.owl-loading { + opacity: 0; + display: block; } + .owl-carousel.owl-hidden { + opacity: 0; } + .owl-carousel.owl-refresh .owl-item { + visibility: hidden; } + .owl-carousel.owl-drag .owl-item { + -ms-touch-action: pan-y; + touch-action: pan-y; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .owl-carousel.owl-grab { + cursor: move; + cursor: grab; } + .owl-carousel.owl-rtl { + direction: rtl; } + .owl-carousel.owl-rtl .owl-item { + float: right; } + +/* No Js */ +.no-js .owl-carousel { + display: block; } + +/* + * Owl Carousel - Animate Plugin + */ +.owl-carousel .animated { + animation-duration: 1000ms; + animation-fill-mode: both; } + +.owl-carousel .owl-animated-in { + z-index: 0; } + +.owl-carousel .owl-animated-out { + z-index: 1; } + +.owl-carousel .fadeOut { + animation-name: fadeOut; } + +@keyframes fadeOut { + 0% { + opacity: 1; } + 100% { + opacity: 0; } } + +/* + * Owl Carousel - Auto Height Plugin + */ +.owl-height { + transition: height 500ms ease-in-out; } + +/* + * Owl Carousel - Lazy Load Plugin + */ +.owl-carousel .owl-item { + /** + This is introduced due to a bug in IE11 where lazy loading combined with autoheight plugin causes a wrong + calculation of the height of the owl-item that breaks page layouts + */ } + .owl-carousel .owl-item .owl-lazy { + opacity: 0; + transition: opacity 400ms ease; } + .owl-carousel .owl-item .owl-lazy[src^=""], .owl-carousel .owl-item .owl-lazy:not([src]) { + max-height: 0; } + .owl-carousel .owl-item img.owl-lazy { + transform-style: preserve-3d; } + +/* + * Owl Carousel - Video Plugin + */ +.owl-carousel .owl-video-wrapper { + position: relative; + height: 100%; + background: #000; } + +.owl-carousel .owl-video-play-icon { + position: absolute; + height: 80px; + width: 80px; + left: 50%; + top: 50%; + margin-left: -40px; + margin-top: -40px; + background: url("owl.video.play.png") no-repeat; + cursor: pointer; + z-index: 1; + -webkit-backface-visibility: hidden; + transition: transform 100ms ease; } + +.owl-carousel .owl-video-play-icon:hover { + -ms-transform: scale(1.3, 1.3); + transform: scale(1.3, 1.3); } + +.owl-carousel .owl-video-playing .owl-video-tn, +.owl-carousel .owl-video-playing .owl-video-play-icon { + display: none; } + +.owl-carousel .owl-video-tn { + opacity: 0; + height: 100%; + background-position: center center; + background-repeat: no-repeat; + background-size: contain; + transition: opacity 400ms ease; } + +.owl-carousel .owl-video-frame { + position: relative; + z-index: 1; + height: 100%; + width: 100%; } diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.theme.default.css b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.theme.default.css new file mode 100644 index 000000000..e2020fb10 --- /dev/null +++ b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.theme.default.css @@ -0,0 +1,50 @@ +/** + * Owl Carousel v2.3.4 + * Copyright 2013-2018 David Deutsch + * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE + */ +/* + * Default theme - Owl Carousel CSS File + */ +.owl-theme .owl-nav { + margin-top: 10px; + text-align: center; + -webkit-tap-highlight-color: transparent; } + .owl-theme .owl-nav [class*='owl-'] { + color: #FFF; + font-size: 14px; + margin: 5px; + padding: 4px 7px; + background: #D6D6D6; + display: inline-block; + cursor: pointer; + border-radius: 3px; } + .owl-theme .owl-nav [class*='owl-']:hover { + background: #869791; + color: #FFF; + text-decoration: none; } + .owl-theme .owl-nav .disabled { + opacity: 0.5; + cursor: default; } + +.owl-theme .owl-nav.disabled + .owl-dots { + margin-top: 10px; } + +.owl-theme .owl-dots { + text-align: center; + -webkit-tap-highlight-color: transparent; } + .owl-theme .owl-dots .owl-dot { + display: inline-block; + zoom: 1; + *display: inline; } + .owl-theme .owl-dots .owl-dot span { + width: 10px; + height: 10px; + margin: 5px 7px; + background: #D6D6D6; + display: block; + -webkit-backface-visibility: visible; + transition: opacity 200ms ease; + border-radius: 30px; } + .owl-theme .owl-dots .owl-dot.active span, .owl-theme .owl-dots .owl-dot:hover span { + background: #869791; } diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.theme.green.css b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.theme.green.css new file mode 100644 index 000000000..5235fbe3b --- /dev/null +++ b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.theme.green.css @@ -0,0 +1,50 @@ +/** + * Owl Carousel v2.3.4 + * Copyright 2013-2018 David Deutsch + * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE + */ +/* + * Green theme - Owl Carousel CSS File + */ +.owl-theme .owl-nav { + margin-top: 10px; + text-align: center; + -webkit-tap-highlight-color: transparent; } + .owl-theme .owl-nav [class*='owl-'] { + color: #FFF; + font-size: 14px; + margin: 5px; + padding: 4px 7px; + background: #D6D6D6; + display: inline-block; + cursor: pointer; + border-radius: 3px; } + .owl-theme .owl-nav [class*='owl-']:hover { + background: #4DC7A0; + color: #FFF; + text-decoration: none; } + .owl-theme .owl-nav .disabled { + opacity: 0.5; + cursor: default; } + +.owl-theme .owl-nav.disabled + .owl-dots { + margin-top: 10px; } + +.owl-theme .owl-dots { + text-align: center; + -webkit-tap-highlight-color: transparent; } + .owl-theme .owl-dots .owl-dot { + display: inline-block; + zoom: 1; + *display: inline; } + .owl-theme .owl-dots .owl-dot span { + width: 10px; + height: 10px; + margin: 5px 7px; + background: #D6D6D6; + display: block; + -webkit-backface-visibility: visible; + transition: opacity 200ms ease; + border-radius: 30px; } + .owl-theme .owl-dots .owl-dot.active span, .owl-theme .owl-dots .owl-dot:hover span { + background: #4DC7A0; } diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.video.play.png b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.video.play.png new file mode 100644 index 000000000..5d0218d46 Binary files /dev/null and b/theme_prime/static/lib/OwlCarousel2-2.3.4/assets/owl.video.play.png differ diff --git a/theme_prime/static/lib/OwlCarousel2-2.3.4/owl.carousel.js b/theme_prime/static/lib/OwlCarousel2-2.3.4/owl.carousel.js new file mode 100644 index 000000000..66c67ebe0 --- /dev/null +++ b/theme_prime/static/lib/OwlCarousel2-2.3.4/owl.carousel.js @@ -0,0 +1,3448 @@ +/** + * Owl Carousel v2.3.4 + * Copyright 2013-2018 David Deutsch + * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE + */ +/** + * Owl carousel + * @version 2.3.4 + * @author Bartosz Wojciechowski + * @author David Deutsch + * @license The MIT License (MIT) + * @todo Lazy Load Icon + * @todo prevent animationend bubling + * @todo itemsScaleUp + * @todo Test Zepto + * @todo stagePadding calculate wrong active classes + */ +;(function($, window, document, undefined) { + + /** + * Creates a carousel. + * @class The Owl Carousel. + * @public + * @param {HTMLElement|jQuery} element - The element to create the carousel for. + * @param {Object} [options] - The options + */ + function Owl(element, options) { + + /** + * Current settings for the carousel. + * @public + */ + this.settings = null; + + /** + * Current options set by the caller including defaults. + * @public + */ + this.options = $.extend({}, Owl.Defaults, options); + + /** + * Plugin element. + * @public + */ + this.$element = $(element); + + /** + * Proxied event handlers. + * @protected + */ + this._handlers = {}; + + /** + * References to the running plugins of this carousel. + * @protected + */ + this._plugins = {}; + + /** + * Currently suppressed events to prevent them from being retriggered. + * @protected + */ + this._supress = {}; + + /** + * Absolute current position. + * @protected + */ + this._current = null; + + /** + * Animation speed in milliseconds. + * @protected + */ + this._speed = null; + + /** + * Coordinates of all items in pixel. + * @todo The name of this member is missleading. + * @protected + */ + this._coordinates = []; + + /** + * Current breakpoint. + * @todo Real media queries would be nice. + * @protected + */ + this._breakpoint = null; + + /** + * Current width of the plugin element. + */ + this._width = null; + + /** + * All real items. + * @protected + */ + this._items = []; + + /** + * All cloned items. + * @protected + */ + this._clones = []; + + /** + * Merge values of all items. + * @todo Maybe this could be part of a plugin. + * @protected + */ + this._mergers = []; + + /** + * Widths of all items. + */ + this._widths = []; + + /** + * Invalidated parts within the update process. + * @protected + */ + this._invalidated = {}; + + /** + * Ordered list of workers for the update process. + * @protected + */ + this._pipe = []; + + /** + * Current state information for the drag operation. + * @todo #261 + * @protected + */ + this._drag = { + time: null, + target: null, + pointer: null, + stage: { + start: null, + current: null + }, + direction: null + }; + + /** + * Current state information and their tags. + * @type {Object} + * @protected + */ + this._states = { + current: {}, + tags: { + 'initializing': [ 'busy' ], + 'animating': [ 'busy' ], + 'dragging': [ 'interacting' ] + } + }; + + $.each([ 'onResize', 'onThrottledResize' ], $.proxy(function(i, handler) { + this._handlers[handler] = $.proxy(this[handler], this); + }, this)); + + $.each(Owl.Plugins, $.proxy(function(key, plugin) { + this._plugins[key.charAt(0).toLowerCase() + key.slice(1)] + = new plugin(this); + }, this)); + + $.each(Owl.Workers, $.proxy(function(priority, worker) { + this._pipe.push({ + 'filter': worker.filter, + 'run': $.proxy(worker.run, this) + }); + }, this)); + + this.setup(); + this.initialize(); + } + + /** + * Default options for the carousel. + * @public + */ + Owl.Defaults = { + items: 3, + loop: false, + center: false, + rewind: false, + checkVisibility: true, + + mouseDrag: true, + touchDrag: true, + pullDrag: true, + freeDrag: false, + + margin: 0, + stagePadding: 0, + + merge: false, + mergeFit: true, + autoWidth: false, + + startPosition: 0, + rtl: false, + + smartSpeed: 250, + fluidSpeed: false, + dragEndSpeed: false, + + responsive: {}, + responsiveRefreshRate: 200, + responsiveBaseElement: window, + + fallbackEasing: 'swing', + slideTransition: '', + + info: false, + + nestedItemSelector: false, + itemElement: 'div', + stageElement: 'div', + + refreshClass: 'owl-refresh', + loadedClass: 'owl-loaded', + loadingClass: 'owl-loading', + rtlClass: 'owl-rtl', + responsiveClass: 'owl-responsive', + dragClass: 'owl-drag', + itemClass: 'owl-item', + stageClass: 'owl-stage', + stageOuterClass: 'owl-stage-outer', + grabClass: 'owl-grab' + }; + + /** + * Enumeration for width. + * @public + * @readonly + * @enum {String} + */ + Owl.Width = { + Default: 'default', + Inner: 'inner', + Outer: 'outer' + }; + + /** + * Enumeration for types. + * @public + * @readonly + * @enum {String} + */ + Owl.Type = { + Event: 'event', + State: 'state' + }; + + /** + * Contains all registered plugins. + * @public + */ + Owl.Plugins = {}; + + /** + * List of workers involved in the update process. + */ + Owl.Workers = [ { + filter: [ 'width', 'settings' ], + run: function() { + this._width = this.$element.width(); + } + }, { + filter: [ 'width', 'items', 'settings' ], + run: function(cache) { + cache.current = this._items && this._items[this.relative(this._current)]; + } + }, { + filter: [ 'items', 'settings' ], + run: function() { + this.$stage.children('.cloned').remove(); + } + }, { + filter: [ 'width', 'items', 'settings' ], + run: function(cache) { + var margin = this.settings.margin || '', + grid = !this.settings.autoWidth, + rtl = this.settings.rtl, + css = { + 'width': 'auto', + 'margin-left': rtl ? margin : '', + 'margin-right': rtl ? '' : margin + }; + + !grid && this.$stage.children().css(css); + + cache.css = css; + } + }, { + filter: [ 'width', 'items', 'settings' ], + run: function(cache) { + var width = (this.width() / this.settings.items).toFixed(3) - this.settings.margin, + merge = null, + iterator = this._items.length, + grid = !this.settings.autoWidth, + widths = []; + + cache.items = { + merge: false, + width: width + }; + + while (iterator--) { + merge = this._mergers[iterator]; + merge = this.settings.mergeFit && Math.min(merge, this.settings.items) || merge; + + cache.items.merge = merge > 1 || cache.items.merge; + + widths[iterator] = !grid ? this._items[iterator].width() : width * merge; + } + + this._widths = widths; + } + }, { + filter: [ 'items', 'settings' ], + run: function() { + var clones = [], + items = this._items, + settings = this.settings, + // TODO: Should be computed from number of min width items in stage + view = Math.max(settings.items * 2, 4), + size = Math.ceil(items.length / 2) * 2, + repeat = settings.loop && items.length ? settings.rewind ? view : Math.max(view, size) : 0, + append = '', + prepend = ''; + + repeat /= 2; + + while (repeat > 0) { + // Switch to only using appended clones + clones.push(this.normalize(clones.length / 2, true)); + append = append + items[clones[clones.length - 1]][0].outerHTML; + clones.push(this.normalize(items.length - 1 - (clones.length - 1) / 2, true)); + prepend = items[clones[clones.length - 1]][0].outerHTML + prepend; + repeat -= 1; + } + + this._clones = clones; + + $(append).addClass('cloned').appendTo(this.$stage); + $(prepend).addClass('cloned').prependTo(this.$stage); + } + }, { + filter: [ 'width', 'items', 'settings' ], + run: function() { + var rtl = this.settings.rtl ? 1 : -1, + size = this._clones.length + this._items.length, + iterator = -1, + previous = 0, + current = 0, + coordinates = []; + + while (++iterator < size) { + previous = coordinates[iterator - 1] || 0; + current = this._widths[this.relative(iterator)] + this.settings.margin; + coordinates.push(previous + current * rtl); + } + + this._coordinates = coordinates; + } + }, { + filter: [ 'width', 'items', 'settings' ], + run: function() { + var padding = this.settings.stagePadding, + coordinates = this._coordinates, + css = { + 'width': Math.ceil(Math.abs(coordinates[coordinates.length - 1])) + padding * 2, + 'padding-left': padding || '', + 'padding-right': padding || '' + }; + + this.$stage.css(css); + } + }, { + filter: [ 'width', 'items', 'settings' ], + run: function(cache) { + var iterator = this._coordinates.length, + grid = !this.settings.autoWidth, + items = this.$stage.children(); + + if (grid && cache.items.merge) { + while (iterator--) { + cache.css.width = this._widths[this.relative(iterator)]; + items.eq(iterator).css(cache.css); + } + } else if (grid) { + cache.css.width = cache.items.width; + items.css(cache.css); + } + } + }, { + filter: [ 'items' ], + run: function() { + this._coordinates.length < 1 && this.$stage.removeAttr('style'); + } + }, { + filter: [ 'width', 'items', 'settings' ], + run: function(cache) { + cache.current = cache.current ? this.$stage.children().index(cache.current) : 0; + cache.current = Math.max(this.minimum(), Math.min(this.maximum(), cache.current)); + this.reset(cache.current); + } + }, { + filter: [ 'position' ], + run: function() { + this.animate(this.coordinates(this._current)); + } + }, { + filter: [ 'width', 'position', 'items', 'settings' ], + run: function() { + var rtl = this.settings.rtl ? 1 : -1, + padding = this.settings.stagePadding * 2, + begin = this.coordinates(this.current()) + padding, + end = begin + this.width() * rtl, + inner, outer, matches = [], i, n; + + for (i = 0, n = this._coordinates.length; i < n; i++) { + inner = this._coordinates[i - 1] || 0; + outer = Math.abs(this._coordinates[i]) + padding * rtl; + + if ((this.op(inner, '<=', begin) && (this.op(inner, '>', end))) + || (this.op(outer, '<', begin) && this.op(outer, '>', end))) { + matches.push(i); + } + } + + this.$stage.children('.active').removeClass('active'); + this.$stage.children(':eq(' + matches.join('), :eq(') + ')').addClass('active'); + + this.$stage.children('.center').removeClass('center'); + if (this.settings.center) { + this.$stage.children().eq(this.current()).addClass('center'); + } + } + } ]; + + /** + * Create the stage DOM element + */ + Owl.prototype.initializeStage = function() { + this.$stage = this.$element.find('.' + this.settings.stageClass); + + // if the stage is already in the DOM, grab it and skip stage initialization + if (this.$stage.length) { + return; + } + + this.$element.addClass(this.options.loadingClass); + + // create stage + this.$stage = $('<' + this.settings.stageElement + '>', { + "class": this.settings.stageClass + }).wrap( $( '
', { + "class": this.settings.stageOuterClass + })); + + // append stage + this.$element.append(this.$stage.parent()); + }; + + /** + * Create item DOM elements + */ + Owl.prototype.initializeItems = function() { + var $items = this.$element.find('.owl-item'); + + // if the items are already in the DOM, grab them and skip item initialization + if ($items.length) { + this._items = $items.get().map(function(item) { + return $(item); + }); + + this._mergers = this._items.map(function() { + return 1; + }); + + this.refresh(); + + return; + } + + // append content + this.replace(this.$element.children().not(this.$stage.parent())); + + // check visibility + if (this.isVisible()) { + // update view + this.refresh(); + } else { + // invalidate width + this.invalidate('width'); + } + + this.$element + .removeClass(this.options.loadingClass) + .addClass(this.options.loadedClass); + }; + + /** + * Initializes the carousel. + * @protected + */ + Owl.prototype.initialize = function() { + this.enter('initializing'); + this.trigger('initialize'); + + this.$element.toggleClass(this.settings.rtlClass, this.settings.rtl); + + if (this.settings.autoWidth && !this.is('pre-loading')) { + var imgs, nestedSelector, width; + imgs = this.$element.find('img'); + nestedSelector = this.settings.nestedItemSelector ? '.' + this.settings.nestedItemSelector : undefined; + width = this.$element.children(nestedSelector).width(); + + if (imgs.length && width <= 0) { + this.preloadAutoWidthImages(imgs); + } + } + + this.initializeStage(); + this.initializeItems(); + + // register event handlers + this.registerEventHandlers(); + + this.leave('initializing'); + this.trigger('initialized'); + }; + + /** + * @returns {Boolean} visibility of $element + * if you know the carousel will always be visible you can set `checkVisibility` to `false` to + * prevent the expensive browser layout forced reflow the $element.is(':visible') does + */ + Owl.prototype.isVisible = function() { + return this.settings.checkVisibility + ? this.$element.is(':visible') + : true; + }; + + /** + * Setups the current settings. + * @todo Remove responsive classes. Why should adaptive designs be brought into IE8? + * @todo Support for media queries by using `matchMedia` would be nice. + * @public + */ + Owl.prototype.setup = function() { + var viewport = this.viewport(), + overwrites = this.options.responsive, + match = -1, + settings = null; + + if (!overwrites) { + settings = $.extend({}, this.options); + } else { + $.each(overwrites, function(breakpoint) { + if (breakpoint <= viewport && breakpoint > match) { + match = Number(breakpoint); + } + }); + + settings = $.extend({}, this.options, overwrites[match]); + if (typeof settings.stagePadding === 'function') { + settings.stagePadding = settings.stagePadding(); + } + delete settings.responsive; + + // responsive class + if (settings.responsiveClass) { + this.$element.attr('class', + this.$element.attr('class').replace(new RegExp('(' + this.options.responsiveClass + '-)\\S+\\s', 'g'), '$1' + match) + ); + } + } + + this.trigger('change', { property: { name: 'settings', value: settings } }); + this._breakpoint = match; + this.settings = settings; + this.invalidate('settings'); + this.trigger('changed', { property: { name: 'settings', value: this.settings } }); + }; + + /** + * Updates option logic if necessery. + * @protected + */ + Owl.prototype.optionsLogic = function() { + if (this.settings.autoWidth) { + this.settings.stagePadding = false; + this.settings.merge = false; + } + }; + + /** + * Prepares an item before add. + * @todo Rename event parameter `content` to `item`. + * @protected + * @returns {jQuery|HTMLElement} - The item container. + */ + Owl.prototype.prepare = function(item) { + var event = this.trigger('prepare', { content: item }); + + if (!event.data) { + event.data = $('<' + this.settings.itemElement + '/>') + .addClass(this.options.itemClass).append(item) + } + + this.trigger('prepared', { content: event.data }); + + return event.data; + }; + + /** + * Updates the view. + * @public + */ + Owl.prototype.update = function() { + var i = 0, + n = this._pipe.length, + filter = $.proxy(function(p) { return this[p] }, this._invalidated), + cache = {}; + + while (i < n) { + if (this._invalidated.all || $.grep(this._pipe[i].filter, filter).length > 0) { + this._pipe[i].run(cache); + } + i++; + } + + this._invalidated = {}; + + !this.is('valid') && this.enter('valid'); + }; + + /** + * Gets the width of the view. + * @public + * @param {Owl.Width} [dimension=Owl.Width.Default] - The dimension to return. + * @returns {Number} - The width of the view in pixel. + */ + Owl.prototype.width = function(dimension) { + dimension = dimension || Owl.Width.Default; + switch (dimension) { + case Owl.Width.Inner: + case Owl.Width.Outer: + return this._width; + default: + return this._width - this.settings.stagePadding * 2 + this.settings.margin; + } + }; + + /** + * Refreshes the carousel primarily for adaptive purposes. + * @public + */ + Owl.prototype.refresh = function() { + this.enter('refreshing'); + this.trigger('refresh'); + + this.setup(); + + this.optionsLogic(); + + this.$element.addClass(this.options.refreshClass); + + this.update(); + + this.$element.removeClass(this.options.refreshClass); + + this.leave('refreshing'); + this.trigger('refreshed'); + }; + + /** + * Checks window `resize` event. + * @protected + */ + Owl.prototype.onThrottledResize = function() { + window.clearTimeout(this.resizeTimer); + this.resizeTimer = window.setTimeout(this._handlers.onResize, this.settings.responsiveRefreshRate); + }; + + /** + * Checks window `resize` event. + * @protected + */ + Owl.prototype.onResize = function() { + if (!this._items.length) { + return false; + } + + if (this._width === this.$element.width()) { + return false; + } + + if (!this.isVisible()) { + return false; + } + + this.enter('resizing'); + + if (this.trigger('resize').isDefaultPrevented()) { + this.leave('resizing'); + return false; + } + + this.invalidate('width'); + + this.refresh(); + + this.leave('resizing'); + this.trigger('resized'); + }; + + /** + * Registers event handlers. + * @todo Check `msPointerEnabled` + * @todo #261 + * @protected + */ + Owl.prototype.registerEventHandlers = function() { + if ($.support.transition) { + this.$stage.on($.support.transition.end + '.owl.core', $.proxy(this.onTransitionEnd, this)); + } + + if (this.settings.responsive !== false) { + this.on(window, 'resize', this._handlers.onThrottledResize); + } + + if (this.settings.mouseDrag) { + this.$element.addClass(this.options.dragClass); + this.$stage.on('mousedown.owl.core', $.proxy(this.onDragStart, this)); + this.$stage.on('dragstart.owl.core selectstart.owl.core', function() { return false }); + } + + if (this.settings.touchDrag){ + this.$stage.on('touchstart.owl.core', $.proxy(this.onDragStart, this)); + this.$stage.on('touchcancel.owl.core', $.proxy(this.onDragEnd, this)); + } + }; + + /** + * Handles `touchstart` and `mousedown` events. + * @todo Horizontal swipe threshold as option + * @todo #261 + * @protected + * @param {Event} event - The event arguments. + */ + Owl.prototype.onDragStart = function(event) { + var stage = null; + + if (event.which === 3) { + return; + } + + if ($.support.transform) { + stage = this.$stage.css('transform').replace(/.*\(|\)| /g, '').split(','); + stage = { + x: stage[stage.length === 16 ? 12 : 4], + y: stage[stage.length === 16 ? 13 : 5] + }; + } else { + stage = this.$stage.position(); + stage = { + x: this.settings.rtl ? + stage.left + this.$stage.width() - this.width() + this.settings.margin : + stage.left, + y: stage.top + }; + } + + if (this.is('animating')) { + $.support.transform ? this.animate(stage.x) : this.$stage.stop() + this.invalidate('position'); + } + + this.$element.toggleClass(this.options.grabClass, event.type === 'mousedown'); + + this.speed(0); + + this._drag.time = new Date().getTime(); + this._drag.target = $(event.target); + this._drag.stage.start = stage; + this._drag.stage.current = stage; + this._drag.pointer = this.pointer(event); + + $(document).on('mouseup.owl.core touchend.owl.core', $.proxy(this.onDragEnd, this)); + + $(document).one('mousemove.owl.core touchmove.owl.core', $.proxy(function(event) { + var delta = this.difference(this._drag.pointer, this.pointer(event)); + + $(document).on('mousemove.owl.core touchmove.owl.core', $.proxy(this.onDragMove, this)); + + if (Math.abs(delta.x) < Math.abs(delta.y) && this.is('valid')) { + return; + } + + event.preventDefault(); + + this.enter('dragging'); + this.trigger('drag'); + }, this)); + }; + + /** + * Handles the `touchmove` and `mousemove` events. + * @todo #261 + * @protected + * @param {Event} event - The event arguments. + */ + Owl.prototype.onDragMove = function(event) { + var minimum = null, + maximum = null, + pull = null, + delta = this.difference(this._drag.pointer, this.pointer(event)), + stage = this.difference(this._drag.stage.start, delta); + + if (!this.is('dragging')) { + return; + } + + event.preventDefault(); + + if (this.settings.loop) { + minimum = this.coordinates(this.minimum()); + maximum = this.coordinates(this.maximum() + 1) - minimum; + stage.x = (((stage.x - minimum) % maximum + maximum) % maximum) + minimum; + } else { + minimum = this.settings.rtl ? this.coordinates(this.maximum()) : this.coordinates(this.minimum()); + maximum = this.settings.rtl ? this.coordinates(this.minimum()) : this.coordinates(this.maximum()); + pull = this.settings.pullDrag ? -1 * delta.x / 5 : 0; + stage.x = Math.max(Math.min(stage.x, minimum + pull), maximum + pull); + } + + this._drag.stage.current = stage; + + this.animate(stage.x); + }; + + /** + * Handles the `touchend` and `mouseup` events. + * @todo #261 + * @todo Threshold for click event + * @protected + * @param {Event} event - The event arguments. + */ + Owl.prototype.onDragEnd = function(event) { + var delta = this.difference(this._drag.pointer, this.pointer(event)), + stage = this._drag.stage.current, + direction = delta.x > 0 ^ this.settings.rtl ? 'left' : 'right'; + + $(document).off('.owl.core'); + + this.$element.removeClass(this.options.grabClass); + + if (delta.x !== 0 && this.is('dragging') || !this.is('valid')) { + this.speed(this.settings.dragEndSpeed || this.settings.smartSpeed); + this.current(this.closest(stage.x, delta.x !== 0 ? direction : this._drag.direction)); + this.invalidate('position'); + this.update(); + + this._drag.direction = direction; + + if (Math.abs(delta.x) > 3 || new Date().getTime() - this._drag.time > 300) { + this._drag.target.one('click.owl.core', function() { return false; }); + } + } + + if (!this.is('dragging')) { + return; + } + + this.leave('dragging'); + this.trigger('dragged'); + }; + + /** + * Gets absolute position of the closest item for a coordinate. + * @todo Setting `freeDrag` makes `closest` not reusable. See #165. + * @protected + * @param {Number} coordinate - The coordinate in pixel. + * @param {String} direction - The direction to check for the closest item. Ether `left` or `right`. + * @return {Number} - The absolute position of the closest item. + */ + Owl.prototype.closest = function(coordinate, direction) { + var position = -1, + pull = 30, + width = this.width(), + coordinates = this.coordinates(); + + if (!this.settings.freeDrag) { + // check closest item + $.each(coordinates, $.proxy(function(index, value) { + // on a left pull, check on current index + if (direction === 'left' && coordinate > value - pull && coordinate < value + pull) { + position = index; + // on a right pull, check on previous index + // to do so, subtract width from value and set position = index + 1 + } else if (direction === 'right' && coordinate > value - width - pull && coordinate < value - width + pull) { + position = index + 1; + } else if (this.op(coordinate, '<', value) + && this.op(coordinate, '>', coordinates[index + 1] !== undefined ? coordinates[index + 1] : value - width)) { + position = direction === 'left' ? index + 1 : index; + } + return position === -1; + }, this)); + } + + if (!this.settings.loop) { + // non loop boundries + if (this.op(coordinate, '>', coordinates[this.minimum()])) { + position = coordinate = this.minimum(); + } else if (this.op(coordinate, '<', coordinates[this.maximum()])) { + position = coordinate = this.maximum(); + } + } + + return position; + }; + + /** + * Animates the stage. + * @todo #270 + * @public + * @param {Number} coordinate - The coordinate in pixels. + */ + Owl.prototype.animate = function(coordinate) { + var animate = this.speed() > 0; + + this.is('animating') && this.onTransitionEnd(); + + if (animate) { + this.enter('animating'); + this.trigger('translate'); + } + + if ($.support.transform3d && $.support.transition) { + this.$stage.css({ + transform: 'translate3d(' + coordinate + 'px,0px,0px)', + transition: (this.speed() / 1000) + 's' + ( + this.settings.slideTransition ? ' ' + this.settings.slideTransition : '' + ) + }); + } else if (animate) { + this.$stage.animate({ + left: coordinate + 'px' + }, this.speed(), this.settings.fallbackEasing, $.proxy(this.onTransitionEnd, this)); + } else { + this.$stage.css({ + left: coordinate + 'px' + }); + } + }; + + /** + * Checks whether the carousel is in a specific state or not. + * @param {String} state - The state to check. + * @returns {Boolean} - The flag which indicates if the carousel is busy. + */ + Owl.prototype.is = function(state) { + return this._states.current[state] && this._states.current[state] > 0; + }; + + /** + * Sets the absolute position of the current item. + * @public + * @param {Number} [position] - The new absolute position or nothing to leave it unchanged. + * @returns {Number} - The absolute position of the current item. + */ + Owl.prototype.current = function(position) { + if (position === undefined) { + return this._current; + } + + if (this._items.length === 0) { + return undefined; + } + + position = this.normalize(position); + + if (this._current !== position) { + var event = this.trigger('change', { property: { name: 'position', value: position } }); + + if (event.data !== undefined) { + position = this.normalize(event.data); + } + + this._current = position; + + this.invalidate('position'); + + this.trigger('changed', { property: { name: 'position', value: this._current } }); + } + + return this._current; + }; + + /** + * Invalidates the given part of the update routine. + * @param {String} [part] - The part to invalidate. + * @returns {Array.} - The invalidated parts. + */ + Owl.prototype.invalidate = function(part) { + if ($.type(part) === 'string') { + this._invalidated[part] = true; + this.is('valid') && this.leave('valid'); + } + return $.map(this._invalidated, function(v, i) { return i }); + }; + + /** + * Resets the absolute position of the current item. + * @public + * @param {Number} position - The absolute position of the new item. + */ + Owl.prototype.reset = function(position) { + position = this.normalize(position); + + if (position === undefined) { + return; + } + + this._speed = 0; + this._current = position; + + this.suppress([ 'translate', 'translated' ]); + + this.animate(this.coordinates(position)); + + this.release([ 'translate', 'translated' ]); + }; + + /** + * Normalizes an absolute or a relative position of an item. + * @public + * @param {Number} position - The absolute or relative position to normalize. + * @param {Boolean} [relative=false] - Whether the given position is relative or not. + * @returns {Number} - The normalized position. + */ + Owl.prototype.normalize = function(position, relative) { + var n = this._items.length, + m = relative ? 0 : this._clones.length; + + if (!this.isNumeric(position) || n < 1) { + position = undefined; + } else if (position < 0 || position >= n + m) { + position = ((position - m / 2) % n + n) % n + m / 2; + } + + return position; + }; + + /** + * Converts an absolute position of an item into a relative one. + * @public + * @param {Number} position - The absolute position to convert. + * @returns {Number} - The converted position. + */ + Owl.prototype.relative = function(position) { + position -= this._clones.length / 2; + return this.normalize(position, true); + }; + + /** + * Gets the maximum position for the current item. + * @public + * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position. + * @returns {Number} + */ + Owl.prototype.maximum = function(relative) { + var settings = this.settings, + maximum = this._coordinates.length, + iterator, + reciprocalItemsWidth, + elementWidth; + + if (settings.loop) { + maximum = this._clones.length / 2 + this._items.length - 1; + } else if (settings.autoWidth || settings.merge) { + iterator = this._items.length; + if (iterator) { + reciprocalItemsWidth = this._items[--iterator].width(); + elementWidth = this.$element.width(); + while (iterator--) { + reciprocalItemsWidth += this._items[iterator].width() + this.settings.margin; + if (reciprocalItemsWidth > elementWidth) { + break; + } + } + } + maximum = iterator + 1; + } else if (settings.center) { + maximum = this._items.length - 1; + } else { + maximum = this._items.length - settings.items; + } + + if (relative) { + maximum -= this._clones.length / 2; + } + + return Math.max(maximum, 0); + }; + + /** + * Gets the minimum position for the current item. + * @public + * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position. + * @returns {Number} + */ + Owl.prototype.minimum = function(relative) { + return relative ? 0 : this._clones.length / 2; + }; + + /** + * Gets an item at the specified relative position. + * @public + * @param {Number} [position] - The relative position of the item. + * @return {jQuery|Array.} - The item at the given position or all items if no position was given. + */ + Owl.prototype.items = function(position) { + if (position === undefined) { + return this._items.slice(); + } + + position = this.normalize(position, true); + return this._items[position]; + }; + + /** + * Gets an item at the specified relative position. + * @public + * @param {Number} [position] - The relative position of the item. + * @return {jQuery|Array.} - The item at the given position or all items if no position was given. + */ + Owl.prototype.mergers = function(position) { + if (position === undefined) { + return this._mergers.slice(); + } + + position = this.normalize(position, true); + return this._mergers[position]; + }; + + /** + * Gets the absolute positions of clones for an item. + * @public + * @param {Number} [position] - The relative position of the item. + * @returns {Array.} - The absolute positions of clones for the item or all if no position was given. + */ + Owl.prototype.clones = function(position) { + var odd = this._clones.length / 2, + even = odd + this._items.length, + map = function(index) { return index % 2 === 0 ? even + index / 2 : odd - (index + 1) / 2 }; + + if (position === undefined) { + return $.map(this._clones, function(v, i) { return map(i) }); + } + + return $.map(this._clones, function(v, i) { return v === position ? map(i) : null }); + }; + + /** + * Sets the current animation speed. + * @public + * @param {Number} [speed] - The animation speed in milliseconds or nothing to leave it unchanged. + * @returns {Number} - The current animation speed in milliseconds. + */ + Owl.prototype.speed = function(speed) { + if (speed !== undefined) { + this._speed = speed; + } + + return this._speed; + }; + + /** + * Gets the coordinate of an item. + * @todo The name of this method is missleanding. + * @public + * @param {Number} position - The absolute position of the item within `minimum()` and `maximum()`. + * @returns {Number|Array.} - The coordinate of the item in pixel or all coordinates. + */ + Owl.prototype.coordinates = function(position) { + var multiplier = 1, + newPosition = position - 1, + coordinate; + + if (position === undefined) { + return $.map(this._coordinates, $.proxy(function(coordinate, index) { + return this.coordinates(index); + }, this)); + } + + if (this.settings.center) { + if (this.settings.rtl) { + multiplier = -1; + newPosition = position + 1; + } + + coordinate = this._coordinates[position]; + coordinate += (this.width() - coordinate + (this._coordinates[newPosition] || 0)) / 2 * multiplier; + } else { + coordinate = this._coordinates[newPosition] || 0; + } + + coordinate = Math.ceil(coordinate); + + return coordinate; + }; + + /** + * Calculates the speed for a translation. + * @protected + * @param {Number} from - The absolute position of the start item. + * @param {Number} to - The absolute position of the target item. + * @param {Number} [factor=undefined] - The time factor in milliseconds. + * @returns {Number} - The time in milliseconds for the translation. + */ + Owl.prototype.duration = function(from, to, factor) { + if (factor === 0) { + return 0; + } + + return Math.min(Math.max(Math.abs(to - from), 1), 6) * Math.abs((factor || this.settings.smartSpeed)); + }; + + /** + * Slides to the specified item. + * @public + * @param {Number} position - The position of the item. + * @param {Number} [speed] - The time in milliseconds for the transition. + */ + Owl.prototype.to = function(position, speed) { + var current = this.current(), + revert = null, + distance = position - this.relative(current), + direction = (distance > 0) - (distance < 0), + items = this._items.length, + minimum = this.minimum(), + maximum = this.maximum(); + + if (this.settings.loop) { + if (!this.settings.rewind && Math.abs(distance) > items / 2) { + distance += direction * -1 * items; + } + + position = current + distance; + revert = ((position - minimum) % items + items) % items + minimum; + + if (revert !== position && revert - distance <= maximum && revert - distance > 0) { + current = revert - distance; + position = revert; + this.reset(current); + } + } else if (this.settings.rewind) { + maximum += 1; + position = (position % maximum + maximum) % maximum; + } else { + position = Math.max(minimum, Math.min(maximum, position)); + } + + this.speed(this.duration(current, position, speed)); + this.current(position); + + if (this.isVisible()) { + this.update(); + } + }; + + /** + * Slides to the next item. + * @public + * @param {Number} [speed] - The time in milliseconds for the transition. + */ + Owl.prototype.next = function(speed) { + speed = speed || false; + this.to(this.relative(this.current()) + 1, speed); + }; + + /** + * Slides to the previous item. + * @public + * @param {Number} [speed] - The time in milliseconds for the transition. + */ + Owl.prototype.prev = function(speed) { + speed = speed || false; + this.to(this.relative(this.current()) - 1, speed); + }; + + /** + * Handles the end of an animation. + * @protected + * @param {Event} event - The event arguments. + */ + Owl.prototype.onTransitionEnd = function(event) { + + // if css2 animation then event object is undefined + if (event !== undefined) { + event.stopPropagation(); + + // Catch only owl-stage transitionEnd event + if ((event.target || event.srcElement || event.originalTarget) !== this.$stage.get(0)) { + return false; + } + } + + this.leave('animating'); + this.trigger('translated'); + }; + + /** + * Gets viewport width. + * @protected + * @return {Number} - The width in pixel. + */ + Owl.prototype.viewport = function() { + var width; + if (this.options.responsiveBaseElement !== window) { + width = $(this.options.responsiveBaseElement).width(); + } else if (window.innerWidth) { + width = window.innerWidth; + } else if (document.documentElement && document.documentElement.clientWidth) { + width = document.documentElement.clientWidth; + } else { + console.warn('Can not detect viewport width.'); + } + return width; + }; + + /** + * Replaces the current content. + * @public + * @param {HTMLElement|jQuery|String} content - The new content. + */ + Owl.prototype.replace = function(content) { + this.$stage.empty(); + this._items = []; + + if (content) { + content = (content instanceof jQuery) ? content : $(content); + } + + if (this.settings.nestedItemSelector) { + content = content.find('.' + this.settings.nestedItemSelector); + } + + content.filter(function() { + return this.nodeType === 1; + }).each($.proxy(function(index, item) { + item = this.prepare(item); + this.$stage.append(item); + this._items.push(item); + this._mergers.push(item.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1); + }, this)); + + this.reset(this.isNumeric(this.settings.startPosition) ? this.settings.startPosition : 0); + + this.invalidate('items'); + }; + + /** + * Adds an item. + * @todo Use `item` instead of `content` for the event arguments. + * @public + * @param {HTMLElement|jQuery|String} content - The item content to add. + * @param {Number} [position] - The relative position at which to insert the item otherwise the item will be added to the end. + */ + Owl.prototype.add = function(content, position) { + var current = this.relative(this._current); + + position = position === undefined ? this._items.length : this.normalize(position, true); + content = content instanceof jQuery ? content : $(content); + + this.trigger('add', { content: content, position: position }); + + content = this.prepare(content); + + if (this._items.length === 0 || position === this._items.length) { + this._items.length === 0 && this.$stage.append(content); + this._items.length !== 0 && this._items[position - 1].after(content); + this._items.push(content); + this._mergers.push(content.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1); + } else { + this._items[position].before(content); + this._items.splice(position, 0, content); + this._mergers.splice(position, 0, content.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1); + } + + this._items[current] && this.reset(this._items[current].index()); + + this.invalidate('items'); + + this.trigger('added', { content: content, position: position }); + }; + + /** + * Removes an item by its position. + * @todo Use `item` instead of `content` for the event arguments. + * @public + * @param {Number} position - The relative position of the item to remove. + */ + Owl.prototype.remove = function(position) { + position = this.normalize(position, true); + + if (position === undefined) { + return; + } + + this.trigger('remove', { content: this._items[position], position: position }); + + this._items[position].remove(); + this._items.splice(position, 1); + this._mergers.splice(position, 1); + + this.invalidate('items'); + + this.trigger('removed', { content: null, position: position }); + }; + + /** + * Preloads images with auto width. + * @todo Replace by a more generic approach + * @protected + */ + Owl.prototype.preloadAutoWidthImages = function(images) { + images.each($.proxy(function(i, element) { + this.enter('pre-loading'); + element = $(element); + $(new Image()).one('load', $.proxy(function(e) { + element.attr('src', e.target.src); + element.css('opacity', 1); + this.leave('pre-loading'); + !this.is('pre-loading') && !this.is('initializing') && this.refresh(); + }, this)).attr('src', element.attr('src') || element.attr('data-src') || element.attr('data-src-retina')); + }, this)); + }; + + /** + * Destroys the carousel. + * @public + */ + Owl.prototype.destroy = function() { + + this.$element.off('.owl.core'); + this.$stage.off('.owl.core'); + $(document).off('.owl.core'); + + if (this.settings.responsive !== false) { + window.clearTimeout(this.resizeTimer); + this.off(window, 'resize', this._handlers.onThrottledResize); + } + + for (var i in this._plugins) { + this._plugins[i].destroy(); + } + + this.$stage.children('.cloned').remove(); + + this.$stage.unwrap(); + this.$stage.children().contents().unwrap(); + this.$stage.children().unwrap(); + this.$stage.remove(); + this.$element + .removeClass(this.options.refreshClass) + .removeClass(this.options.loadingClass) + .removeClass(this.options.loadedClass) + .removeClass(this.options.rtlClass) + .removeClass(this.options.dragClass) + .removeClass(this.options.grabClass) + .attr('class', this.$element.attr('class').replace(new RegExp(this.options.responsiveClass + '-\\S+\\s', 'g'), '')) + .removeData('owl.carousel'); + }; + + /** + * Operators to calculate right-to-left and left-to-right. + * @protected + * @param {Number} [a] - The left side operand. + * @param {String} [o] - The operator. + * @param {Number} [b] - The right side operand. + */ + Owl.prototype.op = function(a, o, b) { + var rtl = this.settings.rtl; + switch (o) { + case '<': + return rtl ? a > b : a < b; + case '>': + return rtl ? a < b : a > b; + case '>=': + return rtl ? a <= b : a >= b; + case '<=': + return rtl ? a >= b : a <= b; + default: + break; + } + }; + + /** + * Attaches to an internal event. + * @protected + * @param {HTMLElement} element - The event source. + * @param {String} event - The event name. + * @param {Function} listener - The event handler to attach. + * @param {Boolean} capture - Wether the event should be handled at the capturing phase or not. + */ + Owl.prototype.on = function(element, event, listener, capture) { + if (element.addEventListener) { + element.addEventListener(event, listener, capture); + } else if (element.attachEvent) { + element.attachEvent('on' + event, listener); + } + }; + + /** + * Detaches from an internal event. + * @protected + * @param {HTMLElement} element - The event source. + * @param {String} event - The event name. + * @param {Function} listener - The attached event handler to detach. + * @param {Boolean} capture - Wether the attached event handler was registered as a capturing listener or not. + */ + Owl.prototype.off = function(element, event, listener, capture) { + if (element.removeEventListener) { + element.removeEventListener(event, listener, capture); + } else if (element.detachEvent) { + element.detachEvent('on' + event, listener); + } + }; + + /** + * Triggers a public event. + * @todo Remove `status`, `relatedTarget` should be used instead. + * @protected + * @param {String} name - The event name. + * @param {*} [data=null] - The event data. + * @param {String} [namespace=carousel] - The event namespace. + * @param {String} [state] - The state which is associated with the event. + * @param {Boolean} [enter=false] - Indicates if the call enters the specified state or not. + * @returns {Event} - The event arguments. + */ + Owl.prototype.trigger = function(name, data, namespace, state, enter) { + var status = { + item: { count: this._items.length, index: this.current() } + }, handler = $.camelCase( + $.grep([ 'on', name, namespace ], function(v) { return v }) + .join('-').toLowerCase() + ), event = $.Event( + [ name, 'owl', namespace || 'carousel' ].join('.').toLowerCase(), + $.extend({ relatedTarget: this }, status, data) + ); + + if (!this._supress[name]) { + $.each(this._plugins, function(name, plugin) { + if (plugin.onTrigger) { + plugin.onTrigger(event); + } + }); + + this.register({ type: Owl.Type.Event, name: name }); + this.$element.trigger(event); + + if (this.settings && typeof this.settings[handler] === 'function') { + this.settings[handler].call(this, event); + } + } + + return event; + }; + + /** + * Enters a state. + * @param name - The state name. + */ + Owl.prototype.enter = function(name) { + $.each([ name ].concat(this._states.tags[name] || []), $.proxy(function(i, name) { + if (this._states.current[name] === undefined) { + this._states.current[name] = 0; + } + + this._states.current[name]++; + }, this)); + }; + + /** + * Leaves a state. + * @param name - The state name. + */ + Owl.prototype.leave = function(name) { + $.each([ name ].concat(this._states.tags[name] || []), $.proxy(function(i, name) { + this._states.current[name]--; + }, this)); + }; + + /** + * Registers an event or state. + * @public + * @param {Object} object - The event or state to register. + */ + Owl.prototype.register = function(object) { + if (object.type === Owl.Type.Event) { + if (!$.event.special[object.name]) { + $.event.special[object.name] = {}; + } + + if (!$.event.special[object.name].owl) { + var _default = $.event.special[object.name]._default; + $.event.special[object.name]._default = function(e) { + if (_default && _default.apply && (!e.namespace || e.namespace.indexOf('owl') === -1)) { + return _default.apply(this, arguments); + } + return e.namespace && e.namespace.indexOf('owl') > -1; + }; + $.event.special[object.name].owl = true; + } + } else if (object.type === Owl.Type.State) { + if (!this._states.tags[object.name]) { + this._states.tags[object.name] = object.tags; + } else { + this._states.tags[object.name] = this._states.tags[object.name].concat(object.tags); + } + + this._states.tags[object.name] = $.grep(this._states.tags[object.name], $.proxy(function(tag, i) { + return $.inArray(tag, this._states.tags[object.name]) === i; + }, this)); + } + }; + + /** + * Suppresses events. + * @protected + * @param {Array.} events - The events to suppress. + */ + Owl.prototype.suppress = function(events) { + $.each(events, $.proxy(function(index, event) { + this._supress[event] = true; + }, this)); + }; + + /** + * Releases suppressed events. + * @protected + * @param {Array.} events - The events to release. + */ + Owl.prototype.release = function(events) { + $.each(events, $.proxy(function(index, event) { + delete this._supress[event]; + }, this)); + }; + + /** + * Gets unified pointer coordinates from event. + * @todo #261 + * @protected + * @param {Event} - The `mousedown` or `touchstart` event. + * @returns {Object} - Contains `x` and `y` coordinates of current pointer position. + */ + Owl.prototype.pointer = function(event) { + var result = { x: null, y: null }; + + event = event.originalEvent || event || window.event; + + event = event.touches && event.touches.length ? + event.touches[0] : event.changedTouches && event.changedTouches.length ? + event.changedTouches[0] : event; + + if (event.pageX) { + result.x = event.pageX; + result.y = event.pageY; + } else { + result.x = event.clientX; + result.y = event.clientY; + } + + return result; + }; + + /** + * Determines if the input is a Number or something that can be coerced to a Number + * @protected + * @param {Number|String|Object|Array|Boolean|RegExp|Function|Symbol} - The input to be tested + * @returns {Boolean} - An indication if the input is a Number or can be coerced to a Number + */ + Owl.prototype.isNumeric = function(number) { + return !isNaN(parseFloat(number)); + }; + + /** + * Gets the difference of two vectors. + * @todo #261 + * @protected + * @param {Object} - The first vector. + * @param {Object} - The second vector. + * @returns {Object} - The difference. + */ + Owl.prototype.difference = function(first, second) { + return { + x: first.x - second.x, + y: first.y - second.y + }; + }; + + /** + * The jQuery Plugin for the Owl Carousel + * @todo Navigation plugin `next` and `prev` + * @public + */ + $.fn.owlCarousel = function(option) { + var args = Array.prototype.slice.call(arguments, 1); + + return this.each(function() { + var $this = $(this), + data = $this.data('owl.carousel'); + + if (!data) { + data = new Owl(this, typeof option == 'object' && option); + $this.data('owl.carousel', data); + + $.each([ + 'next', 'prev', 'to', 'destroy', 'refresh', 'replace', 'add', 'remove' + ], function(i, event) { + data.register({ type: Owl.Type.Event, name: event }); + data.$element.on(event + '.owl.carousel.core', $.proxy(function(e) { + if (e.namespace && e.relatedTarget !== this) { + this.suppress([ event ]); + data[event].apply(this, [].slice.call(arguments, 1)); + this.release([ event ]); + } + }, data)); + }); + } + + if (typeof option == 'string' && option.charAt(0) !== '_') { + data[option].apply(data, args); + } + }); + }; + + /** + * The constructor for the jQuery Plugin + * @public + */ + $.fn.owlCarousel.Constructor = Owl; + +})(window.Zepto || window.jQuery, window, document); + +/** + * AutoRefresh Plugin + * @version 2.3.4 + * @author Artus Kolanowski + * @author David Deutsch + * @license The MIT License (MIT) + */ +;(function($, window, document, undefined) { + + /** + * Creates the auto refresh plugin. + * @class The Auto Refresh Plugin + * @param {Owl} carousel - The Owl Carousel + */ + var AutoRefresh = function(carousel) { + /** + * Reference to the core. + * @protected + * @type {Owl} + */ + this._core = carousel; + + /** + * Refresh interval. + * @protected + * @type {number} + */ + this._interval = null; + + /** + * Whether the element is currently visible or not. + * @protected + * @type {Boolean} + */ + this._visible = null; + + /** + * All event handlers. + * @protected + * @type {Object} + */ + this._handlers = { + 'initialized.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.autoRefresh) { + this.watch(); + } + }, this) + }; + + // set default options + this._core.options = $.extend({}, AutoRefresh.Defaults, this._core.options); + + // register event handlers + this._core.$element.on(this._handlers); + }; + + /** + * Default options. + * @public + */ + AutoRefresh.Defaults = { + autoRefresh: true, + autoRefreshInterval: 500 + }; + + /** + * Watches the element. + */ + AutoRefresh.prototype.watch = function() { + if (this._interval) { + return; + } + + this._visible = this._core.isVisible(); + this._interval = window.setInterval($.proxy(this.refresh, this), this._core.settings.autoRefreshInterval); + }; + + /** + * Refreshes the element. + */ + AutoRefresh.prototype.refresh = function() { + if (this._core.isVisible() === this._visible) { + return; + } + + this._visible = !this._visible; + + this._core.$element.toggleClass('owl-hidden', !this._visible); + + this._visible && (this._core.invalidate('width') && this._core.refresh()); + }; + + /** + * Destroys the plugin. + */ + AutoRefresh.prototype.destroy = function() { + var handler, property; + + window.clearInterval(this._interval); + + for (handler in this._handlers) { + this._core.$element.off(handler, this._handlers[handler]); + } + for (property in Object.getOwnPropertyNames(this)) { + typeof this[property] != 'function' && (this[property] = null); + } + }; + + $.fn.owlCarousel.Constructor.Plugins.AutoRefresh = AutoRefresh; + +})(window.Zepto || window.jQuery, window, document); + +/** + * Lazy Plugin + * @version 2.3.4 + * @author Bartosz Wojciechowski + * @author David Deutsch + * @license The MIT License (MIT) + */ +;(function($, window, document, undefined) { + + /** + * Creates the lazy plugin. + * @class The Lazy Plugin + * @param {Owl} carousel - The Owl Carousel + */ + var Lazy = function(carousel) { + + /** + * Reference to the core. + * @protected + * @type {Owl} + */ + this._core = carousel; + + /** + * Already loaded items. + * @protected + * @type {Array.} + */ + this._loaded = []; + + /** + * Event handlers. + * @protected + * @type {Object} + */ + this._handlers = { + 'initialized.owl.carousel change.owl.carousel resized.owl.carousel': $.proxy(function(e) { + if (!e.namespace) { + return; + } + + if (!this._core.settings || !this._core.settings.lazyLoad) { + return; + } + + if ((e.property && e.property.name == 'position') || e.type == 'initialized') { + var settings = this._core.settings, + n = (settings.center && Math.ceil(settings.items / 2) || settings.items), + i = ((settings.center && n * -1) || 0), + position = (e.property && e.property.value !== undefined ? e.property.value : this._core.current()) + i, + clones = this._core.clones().length, + load = $.proxy(function(i, v) { this.load(v) }, this); + //TODO: Need documentation for this new option + if (settings.lazyLoadEager > 0) { + n += settings.lazyLoadEager; + // If the carousel is looping also preload images that are to the "left" + if (settings.loop) { + position -= settings.lazyLoadEager; + n++; + } + } + + while (i++ < n) { + this.load(clones / 2 + this._core.relative(position)); + clones && $.each(this._core.clones(this._core.relative(position)), load); + position++; + } + } + }, this) + }; + + // set the default options + this._core.options = $.extend({}, Lazy.Defaults, this._core.options); + + // register event handler + this._core.$element.on(this._handlers); + }; + + /** + * Default options. + * @public + */ + Lazy.Defaults = { + lazyLoad: false, + lazyLoadEager: 0 + }; + + /** + * Loads all resources of an item at the specified position. + * @param {Number} position - The absolute position of the item. + * @protected + */ + Lazy.prototype.load = function(position) { + var $item = this._core.$stage.children().eq(position), + $elements = $item && $item.find('.owl-lazy'); + + if (!$elements || $.inArray($item.get(0), this._loaded) > -1) { + return; + } + + $elements.each($.proxy(function(index, element) { + var $element = $(element), image, + url = (window.devicePixelRatio > 1 && $element.attr('data-src-retina')) || $element.attr('data-src') || $element.attr('data-srcset'); + + this._core.trigger('load', { element: $element, url: url }, 'lazy'); + + if ($element.is('img')) { + $element.one('load.owl.lazy', $.proxy(function() { + $element.css('opacity', 1); + this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); + }, this)).attr('src', url); + } else if ($element.is('source')) { + $element.one('load.owl.lazy', $.proxy(function() { + this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); + }, this)).attr('srcset', url); + } else { + image = new Image(); + image.onload = $.proxy(function() { + $element.css({ + 'background-image': 'url("' + url + '")', + 'opacity': '1' + }); + this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); + }, this); + image.src = url; + } + }, this)); + + this._loaded.push($item.get(0)); + }; + + /** + * Destroys the plugin. + * @public + */ + Lazy.prototype.destroy = function() { + var handler, property; + + for (handler in this.handlers) { + this._core.$element.off(handler, this.handlers[handler]); + } + for (property in Object.getOwnPropertyNames(this)) { + typeof this[property] != 'function' && (this[property] = null); + } + }; + + $.fn.owlCarousel.Constructor.Plugins.Lazy = Lazy; + +})(window.Zepto || window.jQuery, window, document); + +/** + * AutoHeight Plugin + * @version 2.3.4 + * @author Bartosz Wojciechowski + * @author David Deutsch + * @license The MIT License (MIT) + */ +;(function($, window, document, undefined) { + + /** + * Creates the auto height plugin. + * @class The Auto Height Plugin + * @param {Owl} carousel - The Owl Carousel + */ + var AutoHeight = function(carousel) { + /** + * Reference to the core. + * @protected + * @type {Owl} + */ + this._core = carousel; + + this._previousHeight = null; + + /** + * All event handlers. + * @protected + * @type {Object} + */ + this._handlers = { + 'initialized.owl.carousel refreshed.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.autoHeight) { + this.update(); + } + }, this), + 'changed.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.autoHeight && e.property.name === 'position'){ + this.update(); + } + }, this), + 'loaded.owl.lazy': $.proxy(function(e) { + if (e.namespace && this._core.settings.autoHeight + && e.element.closest('.' + this._core.settings.itemClass).index() === this._core.current()) { + this.update(); + } + }, this) + }; + + // set default options + this._core.options = $.extend({}, AutoHeight.Defaults, this._core.options); + + // register event handlers + this._core.$element.on(this._handlers); + this._intervalId = null; + var refThis = this; + + // These changes have been taken from a PR by gavrochelegnou proposed in #1575 + // and have been made compatible with the latest jQuery version + $(window).on('load', function() { + if (refThis._core.settings.autoHeight) { + refThis.update(); + } + }); + + // Autoresize the height of the carousel when window is resized + // When carousel has images, the height is dependent on the width + // and should also change on resize + $(window).resize(function() { + if (refThis._core.settings.autoHeight) { + if (refThis._intervalId != null) { + clearTimeout(refThis._intervalId); + } + + refThis._intervalId = setTimeout(function() { + refThis.update(); + }, 250); + } + }); + + }; + + /** + * Default options. + * @public + */ + AutoHeight.Defaults = { + autoHeight: false, + autoHeightClass: 'owl-height' + }; + + /** + * Updates the view. + */ + AutoHeight.prototype.update = function() { + var start = this._core._current, + end = start + this._core.settings.items, + lazyLoadEnabled = this._core.settings.lazyLoad, + visible = this._core.$stage.children().toArray().slice(start, end), + heights = [], + maxheight = 0; + + $.each(visible, function(index, item) { + heights.push($(item).height()); + }); + + maxheight = Math.max.apply(null, heights); + + if (maxheight <= 1 && lazyLoadEnabled && this._previousHeight) { + maxheight = this._previousHeight; + } + + this._previousHeight = maxheight; + + this._core.$stage.parent() + .height(maxheight) + .addClass(this._core.settings.autoHeightClass); + }; + + AutoHeight.prototype.destroy = function() { + var handler, property; + + for (handler in this._handlers) { + this._core.$element.off(handler, this._handlers[handler]); + } + for (property in Object.getOwnPropertyNames(this)) { + typeof this[property] !== 'function' && (this[property] = null); + } + }; + + $.fn.owlCarousel.Constructor.Plugins.AutoHeight = AutoHeight; + +})(window.Zepto || window.jQuery, window, document); + +/** + * Video Plugin + * @version 2.3.4 + * @author Bartosz Wojciechowski + * @author David Deutsch + * @license The MIT License (MIT) + */ +;(function($, window, document, undefined) { + + /** + * Creates the video plugin. + * @class The Video Plugin + * @param {Owl} carousel - The Owl Carousel + */ + var Video = function(carousel) { + /** + * Reference to the core. + * @protected + * @type {Owl} + */ + this._core = carousel; + + /** + * Cache all video URLs. + * @protected + * @type {Object} + */ + this._videos = {}; + + /** + * Current playing item. + * @protected + * @type {jQuery} + */ + this._playing = null; + + /** + * All event handlers. + * @todo The cloned content removale is too late + * @protected + * @type {Object} + */ + this._handlers = { + 'initialized.owl.carousel': $.proxy(function(e) { + if (e.namespace) { + this._core.register({ type: 'state', name: 'playing', tags: [ 'interacting' ] }); + } + }, this), + 'resize.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.video && this.isInFullScreen()) { + e.preventDefault(); + } + }, this), + 'refreshed.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.is('resizing')) { + this._core.$stage.find('.cloned .owl-video-frame').remove(); + } + }, this), + 'changed.owl.carousel': $.proxy(function(e) { + if (e.namespace && e.property.name === 'position' && this._playing) { + this.stop(); + } + }, this), + 'prepared.owl.carousel': $.proxy(function(e) { + if (!e.namespace) { + return; + } + + var $element = $(e.content).find('.owl-video'); + + if ($element.length) { + $element.css('display', 'none'); + this.fetch($element, $(e.content)); + } + }, this) + }; + + // set default options + this._core.options = $.extend({}, Video.Defaults, this._core.options); + + // register event handlers + this._core.$element.on(this._handlers); + + this._core.$element.on('click.owl.video', '.owl-video-play-icon', $.proxy(function(e) { + this.play(e); + }, this)); + }; + + /** + * Default options. + * @public + */ + Video.Defaults = { + video: false, + videoHeight: false, + videoWidth: false + }; + + /** + * Gets the video ID and the type (YouTube/Vimeo/vzaar only). + * @protected + * @param {jQuery} target - The target containing the video data. + * @param {jQuery} item - The item containing the video. + */ + Video.prototype.fetch = function(target, item) { + var type = (function() { + if (target.attr('data-vimeo-id')) { + return 'vimeo'; + } else if (target.attr('data-vzaar-id')) { + return 'vzaar' + } else { + return 'youtube'; + } + })(), + id = target.attr('data-vimeo-id') || target.attr('data-youtube-id') || target.attr('data-vzaar-id'), + width = target.attr('data-width') || this._core.settings.videoWidth, + height = target.attr('data-height') || this._core.settings.videoHeight, + url = target.attr('href'); + + if (url) { + + /* + Parses the id's out of the following urls (and probably more): + https://www.youtube.com/watch?v=:id + https://youtu.be/:id + https://vimeo.com/:id + https://vimeo.com/channels/:channel/:id + https://vimeo.com/groups/:group/videos/:id + https://app.vzaar.com/videos/:id + + Visual example: https://regexper.com/#(http%3A%7Chttps%3A%7C)%5C%2F%5C%2F(player.%7Cwww.%7Capp.)%3F(vimeo%5C.com%7Cyoutu(be%5C.com%7C%5C.be%7Cbe%5C.googleapis%5C.com)%7Cvzaar%5C.com)%5C%2F(video%5C%2F%7Cvideos%5C%2F%7Cembed%5C%2F%7Cchannels%5C%2F.%2B%5C%2F%7Cgroups%5C%2F.%2B%5C%2F%7Cwatch%5C%3Fv%3D%7Cv%5C%2F)%3F(%5BA-Za-z0-9._%25-%5D*)(%5C%26%5CS%2B)%3F + */ + + id = url.match(/(http:|https:|)\/\/(player.|www.|app.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com|be\-nocookie\.com)|vzaar\.com)\/(video\/|videos\/|embed\/|channels\/.+\/|groups\/.+\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/); + + if (id[3].indexOf('youtu') > -1) { + type = 'youtube'; + } else if (id[3].indexOf('vimeo') > -1) { + type = 'vimeo'; + } else if (id[3].indexOf('vzaar') > -1) { + type = 'vzaar'; + } else { + throw new Error('Video URL not supported.'); + } + id = id[6]; + } else { + throw new Error('Missing video URL.'); + } + + this._videos[url] = { + type: type, + id: id, + width: width, + height: height + }; + + item.attr('data-video', url); + + this.thumbnail(target, this._videos[url]); + }; + + /** + * Creates video thumbnail. + * @protected + * @param {jQuery} target - The target containing the video data. + * @param {Object} info - The video info object. + * @see `fetch` + */ + Video.prototype.thumbnail = function(target, video) { + var tnLink, + icon, + path, + dimensions = video.width && video.height ? 'width:' + video.width + 'px;height:' + video.height + 'px;' : '', + customTn = target.find('img'), + srcType = 'src', + lazyClass = '', + settings = this._core.settings, + create = function(path) { + icon = '
'; + + if (settings.lazyLoad) { + tnLink = $('
',{ + "class": 'owl-video-tn ' + lazyClass, + "srcType": path + }); + } else { + tnLink = $( '
', { + "class": "owl-video-tn", + "style": 'opacity:1;background-image:url(' + path + ')' + }); + } + target.after(tnLink); + target.after(icon); + }; + + // wrap video content into owl-video-wrapper div + target.wrap( $( '
', { + "class": "owl-video-wrapper", + "style": dimensions + })); + + if (this._core.settings.lazyLoad) { + srcType = 'data-src'; + lazyClass = 'owl-lazy'; + } + + // custom thumbnail + if (customTn.length) { + create(customTn.attr(srcType)); + customTn.remove(); + return false; + } + + if (video.type === 'youtube') { + path = "//img.youtube.com/vi/" + video.id + "/hqdefault.jpg"; + create(path); + } else if (video.type === 'vimeo') { + $.ajax({ + type: 'GET', + url: '//vimeo.com/api/v2/video/' + video.id + '.json', + jsonp: 'callback', + dataType: 'jsonp', + success: function(data) { + path = data[0].thumbnail_large; + create(path); + } + }); + } else if (video.type === 'vzaar') { + $.ajax({ + type: 'GET', + url: '//vzaar.com/api/videos/' + video.id + '.json', + jsonp: 'callback', + dataType: 'jsonp', + success: function(data) { + path = data.framegrab_url; + create(path); + } + }); + } + }; + + /** + * Stops the current video. + * @public + */ + Video.prototype.stop = function() { + this._core.trigger('stop', null, 'video'); + this._playing.find('.owl-video-frame').remove(); + this._playing.removeClass('owl-video-playing'); + this._playing = null; + this._core.leave('playing'); + this._core.trigger('stopped', null, 'video'); + }; + + /** + * Starts the current video. + * @public + * @param {Event} event - The event arguments. + */ + Video.prototype.play = function(event) { + var target = $(event.target), + item = target.closest('.' + this._core.settings.itemClass), + video = this._videos[item.attr('data-video')], + width = video.width || '100%', + height = video.height || this._core.$stage.height(), + html, + iframe; + + if (this._playing) { + return; + } + + this._core.enter('playing'); + this._core.trigger('play', null, 'video'); + + item = this._core.items(this._core.relative(item.index())); + + this._core.reset(item.index()); + + html = $( '' ); + html.attr( 'height', height ); + html.attr( 'width', width ); + if (video.type === 'youtube') { + html.attr( 'src', '//www.youtube.com/embed/' + video.id + '?autoplay=1&rel=0&v=' + video.id ); + } else if (video.type === 'vimeo') { + html.attr( 'src', '//player.vimeo.com/video/' + video.id + '?autoplay=1' ); + } else if (video.type === 'vzaar') { + html.attr( 'src', '//view.vzaar.com/' + video.id + '/player?autoplay=true' ); + } + + iframe = $(html).wrap( '
' ).insertAfter(item.find('.owl-video')); + + this._playing = item.addClass('owl-video-playing'); + }; + + /** + * Checks whether an video is currently in full screen mode or not. + * @todo Bad style because looks like a readonly method but changes members. + * @protected + * @returns {Boolean} + */ + Video.prototype.isInFullScreen = function() { + var element = document.fullscreenElement || document.mozFullScreenElement || + document.webkitFullscreenElement; + + return element && $(element).parent().hasClass('owl-video-frame'); + }; + + /** + * Destroys the plugin. + */ + Video.prototype.destroy = function() { + var handler, property; + + this._core.$element.off('click.owl.video'); + + for (handler in this._handlers) { + this._core.$element.off(handler, this._handlers[handler]); + } + for (property in Object.getOwnPropertyNames(this)) { + typeof this[property] != 'function' && (this[property] = null); + } + }; + + $.fn.owlCarousel.Constructor.Plugins.Video = Video; + +})(window.Zepto || window.jQuery, window, document); + +/** + * Animate Plugin + * @version 2.3.4 + * @author Bartosz Wojciechowski + * @author David Deutsch + * @license The MIT License (MIT) + */ +;(function($, window, document, undefined) { + + /** + * Creates the animate plugin. + * @class The Navigation Plugin + * @param {Owl} scope - The Owl Carousel + */ + var Animate = function(scope) { + this.core = scope; + this.core.options = $.extend({}, Animate.Defaults, this.core.options); + this.swapping = true; + this.previous = undefined; + this.next = undefined; + + this.handlers = { + 'change.owl.carousel': $.proxy(function(e) { + if (e.namespace && e.property.name == 'position') { + this.previous = this.core.current(); + this.next = e.property.value; + } + }, this), + 'drag.owl.carousel dragged.owl.carousel translated.owl.carousel': $.proxy(function(e) { + if (e.namespace) { + this.swapping = e.type == 'translated'; + } + }, this), + 'translate.owl.carousel': $.proxy(function(e) { + if (e.namespace && this.swapping && (this.core.options.animateOut || this.core.options.animateIn)) { + this.swap(); + } + }, this) + }; + + this.core.$element.on(this.handlers); + }; + + /** + * Default options. + * @public + */ + Animate.Defaults = { + animateOut: false, + animateIn: false + }; + + /** + * Toggles the animation classes whenever an translations starts. + * @protected + * @returns {Boolean|undefined} + */ + Animate.prototype.swap = function() { + + if (this.core.settings.items !== 1) { + return; + } + + if (!$.support.animation || !$.support.transition) { + return; + } + + this.core.speed(0); + + var left, + clear = $.proxy(this.clear, this), + previous = this.core.$stage.children().eq(this.previous), + next = this.core.$stage.children().eq(this.next), + incoming = this.core.settings.animateIn, + outgoing = this.core.settings.animateOut; + + if (this.core.current() === this.previous) { + return; + } + + if (outgoing) { + left = this.core.coordinates(this.previous) - this.core.coordinates(this.next); + previous.one($.support.animation.end, clear) + .css( { 'left': left + 'px' } ) + .addClass('animated owl-animated-out') + .addClass(outgoing); + } + + if (incoming) { + next.one($.support.animation.end, clear) + .addClass('animated owl-animated-in') + .addClass(incoming); + } + }; + + Animate.prototype.clear = function(e) { + $(e.target).css( { 'left': '' } ) + .removeClass('animated owl-animated-out owl-animated-in') + .removeClass(this.core.settings.animateIn) + .removeClass(this.core.settings.animateOut); + this.core.onTransitionEnd(); + }; + + /** + * Destroys the plugin. + * @public + */ + Animate.prototype.destroy = function() { + var handler, property; + + for (handler in this.handlers) { + this.core.$element.off(handler, this.handlers[handler]); + } + for (property in Object.getOwnPropertyNames(this)) { + typeof this[property] != 'function' && (this[property] = null); + } + }; + + $.fn.owlCarousel.Constructor.Plugins.Animate = Animate; + +})(window.Zepto || window.jQuery, window, document); + +/** + * Autoplay Plugin + * @version 2.3.4 + * @author Bartosz Wojciechowski + * @author Artus Kolanowski + * @author David Deutsch + * @author Tom De Caluwé + * @license The MIT License (MIT) + */ +;(function($, window, document, undefined) { + + /** + * Creates the autoplay plugin. + * @class The Autoplay Plugin + * @param {Owl} scope - The Owl Carousel + */ + var Autoplay = function(carousel) { + /** + * Reference to the core. + * @protected + * @type {Owl} + */ + this._core = carousel; + + /** + * The autoplay timeout id. + * @type {Number} + */ + this._call = null; + + /** + * Depending on the state of the plugin, this variable contains either + * the start time of the timer or the current timer value if it's + * paused. Since we start in a paused state we initialize the timer + * value. + * @type {Number} + */ + this._time = 0; + + /** + * Stores the timeout currently used. + * @type {Number} + */ + this._timeout = 0; + + /** + * Indicates whenever the autoplay is paused. + * @type {Boolean} + */ + this._paused = true; + + /** + * All event handlers. + * @protected + * @type {Object} + */ + this._handlers = { + 'changed.owl.carousel': $.proxy(function(e) { + if (e.namespace && e.property.name === 'settings') { + if (this._core.settings.autoplay) { + this.play(); + } else { + this.stop(); + } + } else if (e.namespace && e.property.name === 'position' && this._paused) { + // Reset the timer. This code is triggered when the position + // of the carousel was changed through user interaction. + this._time = 0; + } + }, this), + 'initialized.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.autoplay) { + this.play(); + } + }, this), + 'play.owl.autoplay': $.proxy(function(e, t, s) { + if (e.namespace) { + this.play(t, s); + } + }, this), + 'stop.owl.autoplay': $.proxy(function(e) { + if (e.namespace) { + this.stop(); + } + }, this), + 'mouseover.owl.autoplay': $.proxy(function() { + if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) { + this.pause(); + } + }, this), + 'mouseleave.owl.autoplay': $.proxy(function() { + if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) { + this.play(); + } + }, this), + 'touchstart.owl.core': $.proxy(function() { + if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) { + this.pause(); + } + }, this), + 'touchend.owl.core': $.proxy(function() { + if (this._core.settings.autoplayHoverPause) { + this.play(); + } + }, this) + }; + + // register event handlers + this._core.$element.on(this._handlers); + + // set default options + this._core.options = $.extend({}, Autoplay.Defaults, this._core.options); + }; + + /** + * Default options. + * @public + */ + Autoplay.Defaults = { + autoplay: false, + autoplayTimeout: 5000, + autoplayHoverPause: false, + autoplaySpeed: false + }; + + /** + * Transition to the next slide and set a timeout for the next transition. + * @private + * @param {Number} [speed] - The animation speed for the animations. + */ + Autoplay.prototype._next = function(speed) { + this._call = window.setTimeout( + $.proxy(this._next, this, speed), + this._timeout * (Math.round(this.read() / this._timeout) + 1) - this.read() + ); + + if (this._core.is('interacting') || document.hidden) { + return; + } + this._core.next(speed || this._core.settings.autoplaySpeed); + } + + /** + * Reads the current timer value when the timer is playing. + * @public + */ + Autoplay.prototype.read = function() { + return new Date().getTime() - this._time; + }; + + /** + * Starts the autoplay. + * @public + * @param {Number} [timeout] - The interval before the next animation starts. + * @param {Number} [speed] - The animation speed for the animations. + */ + Autoplay.prototype.play = function(timeout, speed) { + var elapsed; + + if (!this._core.is('rotating')) { + this._core.enter('rotating'); + } + + timeout = timeout || this._core.settings.autoplayTimeout; + + // Calculate the elapsed time since the last transition. If the carousel + // wasn't playing this calculation will yield zero. + elapsed = Math.min(this._time % (this._timeout || timeout), timeout); + + if (this._paused) { + // Start the clock. + this._time = this.read(); + this._paused = false; + } else { + // Clear the active timeout to allow replacement. + window.clearTimeout(this._call); + } + + // Adjust the origin of the timer to match the new timeout value. + this._time += this.read() % timeout - elapsed; + + this._timeout = timeout; + this._call = window.setTimeout($.proxy(this._next, this, speed), timeout - elapsed); + }; + + /** + * Stops the autoplay. + * @public + */ + Autoplay.prototype.stop = function() { + if (this._core.is('rotating')) { + // Reset the clock. + this._time = 0; + this._paused = true; + + window.clearTimeout(this._call); + this._core.leave('rotating'); + } + }; + + /** + * Pauses the autoplay. + * @public + */ + Autoplay.prototype.pause = function() { + if (this._core.is('rotating') && !this._paused) { + // Pause the clock. + this._time = this.read(); + this._paused = true; + + window.clearTimeout(this._call); + } + }; + + /** + * Destroys the plugin. + */ + Autoplay.prototype.destroy = function() { + var handler, property; + + this.stop(); + + for (handler in this._handlers) { + this._core.$element.off(handler, this._handlers[handler]); + } + for (property in Object.getOwnPropertyNames(this)) { + typeof this[property] != 'function' && (this[property] = null); + } + }; + + $.fn.owlCarousel.Constructor.Plugins.autoplay = Autoplay; + +})(window.Zepto || window.jQuery, window, document); + +/** + * Navigation Plugin + * @version 2.3.4 + * @author Artus Kolanowski + * @author David Deutsch + * @license The MIT License (MIT) + */ +;(function($, window, document, undefined) { + 'use strict'; + + /** + * Creates the navigation plugin. + * @class The Navigation Plugin + * @param {Owl} carousel - The Owl Carousel. + */ + var Navigation = function(carousel) { + /** + * Reference to the core. + * @protected + * @type {Owl} + */ + this._core = carousel; + + /** + * Indicates whether the plugin is initialized or not. + * @protected + * @type {Boolean} + */ + this._initialized = false; + + /** + * The current paging indexes. + * @protected + * @type {Array} + */ + this._pages = []; + + /** + * All DOM elements of the user interface. + * @protected + * @type {Object} + */ + this._controls = {}; + + /** + * Markup for an indicator. + * @protected + * @type {Array.} + */ + this._templates = []; + + /** + * The carousel element. + * @type {jQuery} + */ + this.$element = this._core.$element; + + /** + * Overridden methods of the carousel. + * @protected + * @type {Object} + */ + this._overrides = { + next: this._core.next, + prev: this._core.prev, + to: this._core.to + }; + + /** + * All event handlers. + * @protected + * @type {Object} + */ + this._handlers = { + 'prepared.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.dotsData) { + this._templates.push('
' + + $(e.content).find('[data-dot]').addBack('[data-dot]').attr('data-dot') + '
'); + } + }, this), + 'added.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.dotsData) { + this._templates.splice(e.position, 0, this._templates.pop()); + } + }, this), + 'remove.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._core.settings.dotsData) { + this._templates.splice(e.position, 1); + } + }, this), + 'changed.owl.carousel': $.proxy(function(e) { + if (e.namespace && e.property.name == 'position') { + this.draw(); + } + }, this), + 'initialized.owl.carousel': $.proxy(function(e) { + if (e.namespace && !this._initialized) { + this._core.trigger('initialize', null, 'navigation'); + this.initialize(); + this.update(); + this.draw(); + this._initialized = true; + this._core.trigger('initialized', null, 'navigation'); + } + }, this), + 'refreshed.owl.carousel': $.proxy(function(e) { + if (e.namespace && this._initialized) { + this._core.trigger('refresh', null, 'navigation'); + this.update(); + this.draw(); + this._core.trigger('refreshed', null, 'navigation'); + } + }, this) + }; + + // set default options + this._core.options = $.extend({}, Navigation.Defaults, this._core.options); + + // register event handlers + this.$element.on(this._handlers); + }; + + /** + * Default options. + * @public + * @todo Rename `slideBy` to `navBy` + */ + Navigation.Defaults = { + nav: false, + navText: [ + '', + '' + ], + navSpeed: false, + navElement: 'button type="button" role="presentation"', + navContainer: false, + navContainerClass: 'owl-nav', + navClass: [ + 'owl-prev', + 'owl-next' + ], + slideBy: 1, + dotClass: 'owl-dot', + dotsClass: 'owl-dots', + dots: true, + dotsEach: false, + dotsData: false, + dotsSpeed: false, + dotsContainer: false + }; + + /** + * Initializes the layout of the plugin and extends the carousel. + * @protected + */ + Navigation.prototype.initialize = function() { + var override, + settings = this._core.settings; + + // create DOM structure for relative navigation + this._controls.$relative = (settings.navContainer ? $(settings.navContainer) + : $('
').addClass(settings.navContainerClass).appendTo(this.$element)).addClass('disabled'); + + this._controls.$previous = $('<' + settings.navElement + '>') + .addClass(settings.navClass[0]) + .html(settings.navText[0]) + .prependTo(this._controls.$relative) + .on('click', $.proxy(function(e) { + this.prev(settings.navSpeed); + }, this)); + this._controls.$next = $('<' + settings.navElement + '>') + .addClass(settings.navClass[1]) + .html(settings.navText[1]) + .appendTo(this._controls.$relative) + .on('click', $.proxy(function(e) { + this.next(settings.navSpeed); + }, this)); + + // create DOM structure for absolute navigation + if (!settings.dotsData) { + this._templates = [ $(' + + + +
+
+
+
+
+ + + + + +
+
+

Prime Snippet Builder Tool

+
You're editing page:
+
+
+
+
+
+
+ + + + + Desktop + + + + + + Mobile + +
+
+
+
+ + +
+ + + + + How it works? + +
+
+ +
+
+
+
+
+
+
+ phone +
+