$elementor->widgets_manager->register_widget_type( new Widgets\ResourceGrid() );
$elementor->widgets_manager->register_widget_type( new Widgets\VideoGallery() );
$elementor->widgets_manager->register_widget_type( new Widgets\TextCarousel() );
+ $elementor->widgets_manager->register_widget_type( new Widgets\MultimediaCarousel() );
$elementor->widgets_manager->register_widget_type( new Widgets\ModalList() );
$elementor->widgets_manager->register_widget_type( new Widgets\BackgroundImage() );
}
--- /dev/null
+<?php
+
+namespace PhysioAssist\Elementor;
+
+use Elementor\Embed;
+
+class Utils
+{
+ // Create lightbox configuration for opening video in Elmentor popup
+ // This can also be used by other elements like TextBlock for the CTA links
+ public static function lightbox($url, $element_ID) {
+
+ // Video settings for lightbox embed
+ $embed_params = [
+ 'rel' => 0, // Don't show related videos at the end of playback
+ 'showinfo' => 0 // Hide info
+ ];
+
+ $lightbox_options = [
+ 'type' => 'video',
+ 'url' => Embed::get_embed_url($url, $embed_params),
+ 'modalOptions' => [
+ 'id' => 'elementor-lightbox-' . $element_ID,
+ 'entranceAnimation' => 'zoomIn',
+ 'videoAspectRatio' => '169',
+ ],
+ ];
+
+ return wp_json_encode($lightbox_options);
+ }
+
+
+ // We can't guarantee that the video's maxresdefault.jpg will exist (it's not created for non-HD uploads)
+ // So this function will find the highest res image available...
+ // Ref: https://stackoverflow.com/a/20655623
+ public static function youtube_image($id) {
+
+ $resolution = [
+ 'maxresdefault',
+ 'hqdefault', // Might have black bars
+ 'mqdefault', // No black bars on this size normally
+ 'sddefault',
+ 'default'
+ ];
+
+ $url = '';
+
+ for ($x = 0; $x < sizeof($resolution); $x++) {
+ $url = 'https://img.youtube.com/vi/' . $id . '/' . $resolution[$x] . '.jpg';
+ // Make sure we get a 200 OK HTTP response
+ if (strpos(get_headers($url)[0], '200 OK') !== false) {
+ break;
+ }
+ }
+
+ return $url;
+ }
+
+}
--- /dev/null
+<?php
+
+namespace PhysioAssist\Elementor\Widgets;
+
+use Elementor\Embed;
+use Elementor\Widget_Base;
+use Elementor\Controls_Manager;
+use Elementor\Utils as ElementorUtils;
+
+use PhysioAssist\Elementor\Utils;
+
+use function App\template;
+use function App\asset_path;
+
+
+class MultimediaCarousel extends Widget_Base {
+
+ // Widget name / ID
+ public function get_name() {
+ return 'cube-multimedia-carousel';
+ }
+
+ // Elementor widget title
+ public function get_title() {
+ return __( 'Multimedia Carousel', 'cube' );
+ }
+
+ // Elementor interface icon
+ public function get_icon() {
+ return 'eicon-media-carousel';
+ }
+
+ // Where to display the widget in the Elementor interface
+ public function get_categories() {
+ return [ 'theme-elements' ];
+ }
+
+ /**
+ * List of scripts the widget depends on.
+ * Used to set scripts dependencies required to run the widget.
+ *
+ * @since 1.0.0
+ * @access public
+ * @return array Widget scripts dependencies.
+ */
+ public function get_script_depends() {
+
+ wp_register_script('lity', asset_path('scripts/lity.js'), ['jquery'], null, true);
+
+ wp_register_script(
+ 'cube-multimedia-carousel',
+ asset_path('scripts/multimedia-carousel.js'),
+ ['jquery', 'lity'], // Dependencies
+ null, // Version
+ true // In footer?
+ );
+
+ // Use script already registered by Elementor so we don't have to include another copy of Slick
+ return ['jquery-slick', 'cube-multimedia-carousel'];
+ }
+ /**
+ * Register the widget controls.
+ * Adds different input fields to allow the user to change and customize the widget settings.
+ *
+ * @since 1.0.0
+ * @access protected
+ */
+ protected function _register_controls() {
+
+ $this->start_controls_section(
+ 'section_content',
+ [
+ 'label' => __( 'Multimedia Carousel', 'cube' ),
+ ]
+ );
+
+
+ $this->add_control(
+ 'items',
+ [
+ 'label' => __( 'Items', 'cube' ),
+ 'type' => Controls_Manager::REPEATER,
+ 'fields' => [
+
+
+ // TODO: finish video / text fields integration. Think about which fields can be used by both (image?)
+ // TODO: refactor blade partials so same partial can be used by individual carousels + mixed carousel to output content (switch based on item type)
+
+
+
+ [
+ 'name' => 'type',
+ 'label' => __('Slide Type', 'cube'),
+ 'type' => Controls_Manager::SELECT,
+ 'default' => '',
+ 'options' => [
+ '' => __('(Select)'),
+ 'video' => __( 'Video', 'cube' ),
+ 'text' => __( 'Text', 'cube' ),
+ ],
+ ],
+
+
+ [
+ 'name' => 'image',
+ 'label' => __('Image', 'cube'),
+ 'label_block' => true,
+ 'type' => Controls_Manager::MEDIA,
+ 'default' => [
+ 'url' => ElementorUtils::get_placeholder_image_src(),
+ ],
+ 'condition' => [
+ 'type' => ['text', 'video']
+ ],
+ ],
+
+ // Video URL only used for video slides
+ [
+ 'name' => 'url',
+ 'label' => __( 'Video URL', 'cube' ),
+ 'placeholder' => __( 'Enter YouTube link', 'cube' ),
+ 'type' => Controls_Manager::TEXT,
+ 'label_block' => true,
+ 'condition' => [
+ 'type' => 'video'
+ ],
+ ],
+
+ [
+ 'name' => 'title',
+ 'label' => __( 'Title', 'cube' ),
+ 'type' => Controls_Manager::TEXT,
+ 'label_block' => true,
+ 'default' => '',
+ 'condition' => [
+ 'type' => ['text', 'video']
+ ],
+ ],
+ [
+ 'name' => 'subtitle',
+ 'label' => __( 'Subtitle', 'cube' ),
+ 'type' => Controls_Manager::TEXT,
+ 'label_block' => true,
+ 'condition' => [
+ 'type' => ['text', 'video']
+ ],
+ ],
+
+ // Body field is only used for "text" slides
+ [
+ 'name' => 'body',
+ 'label' => __('Body', 'cube'),
+ 'type' => Controls_Manager::WYSIWYG,
+ 'default' => '',
+ 'dynamic' => [
+ 'active' => false,
+ ],
+ 'condition' => [
+ 'type' => 'text'
+ ],
+ ],
+ ],
+ 'title_field' => '{{{ type == "text" ? \'<i class="eicon-text-align-left"></i>\' : "" }}} {{{ type == "video" ? \'<i class="eicon-video-camera"></i>\' : "" }}} {{{ title }}}',
+ ]
+ );
+
+
+
+ $this->end_controls_section();
+ }
+ /**
+ * Render the widget output on the frontend.
+ * Written in PHP and used to generate the final HTML.
+ *
+ * @since 1.0.0
+ * @access protected
+ */
+ protected function render() {
+
+ $items = $this->get_settings('items');
+
+ // Process items to get extra information needed for videos
+ foreach ($items as $index => $item) {
+ if ($item['type'] == 'video') {
+ $item['lightbox'] = Utils::lightbox($item['url'], $this->get_id());
+
+ $video_properties = Embed::get_video_properties($item['url']);
+
+ // If no preview image is set, fetch the one from Youtube
+ if (empty($item['image']['id'])) {
+ $item['preview'] = Utils::youtube_image($video_properties['video_id']);
+ } else {
+ $item['preview'] = wp_get_attachment_image_url($item['image']['id'], 'video-preview');
+ }
+
+ $item['details'] = $item['subtitle']; // Allow us to use the same 'subtitle' field for both slide types
+
+ $items[$index] = $item;
+ }
+ }
+
+ echo template('widgets/multimedia-carousel', compact('items'));
+ }
+
+
+}
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
-use Elementor\Utils;
+use Elementor\Utils as ElementorUtils;
use Elementor\Embed;
+use PhysioAssist\Elementor\Utils;
+
+use function App\template;
class VideoGallery extends Widget_Base {
'label_block' => true,
'type' => Controls_Manager::MEDIA,
'default' => [
- 'url' => Utils::get_placeholder_image_src(),
+ 'url' => ElementorUtils::get_placeholder_image_src(),
],
],
],
// Get videos and process each video URL so it can be used with Elementor lightbox
$videos = array_map(function($video) {
- $video['lightbox'] = self::lightbox($video['url'], $this->get_id());
+ $video['lightbox'] = Utils::lightbox($video['url'], $this->get_id());
$video_properties = Embed::get_video_properties($video['url']);
// If no preview image is set, fetch the one from Youtube
if (empty($video['image']['id'])) {
- $video['preview'] = $this->youtube_image($video_properties['video_id']);
+ $video['preview'] = Utils::youtube_image($video_properties['video_id']);
} else {
$video['preview'] = wp_get_attachment_image_url($video['image']['id'], 'video-preview');
}
}, $videos);
if ($gallery_type == 'carousel') {
- echo \App\template('widgets/video-gallery-carousel', compact('videos'));
+ echo template('widgets/video-gallery-carousel', compact('videos'));
} else {
- echo \App\template('widgets/video-gallery-grid', compact('videos'));
+ echo template('widgets/video-gallery-grid', compact('videos'));
}
}
-
- // Create lightbox configuration for opening video in Elmentor popup
- // This can also be used by other elements like TextBlock for the CTA links
- public static function lightbox($url, $element_ID) {
-
- // Video settings for lightbox embed
- $embed_params = [
- 'rel' => 0, // Don't show related videos at the end of playback
- 'showinfo' => 0 // Hide info
- ];
-
- $lightbox_options = [
- 'type' => 'video',
- 'url' => Embed::get_embed_url($url, $embed_params),
- 'modalOptions' => [
- 'id' => 'elementor-lightbox-' . $element_ID,
- 'entranceAnimation' => 'zoomIn',
- 'videoAspectRatio' => '169',
- ],
- ];
-
- return wp_json_encode($lightbox_options);
- }
-
-
- // We can't guarantee that the video's maxresdefault.jpg will exist (it's not created for non-HD uploads)
- // So this function will find the highest res image available...
- // Ref: https://stackoverflow.com/a/20655623
- function youtube_image($id) {
- $resolution = [
- 'maxresdefault',
- 'hqdefault', // Might have black bars
- 'mqdefault', // No black bars on this size normally
- 'sddefault',
- 'default'
- ];
-
- for ($x = 0; $x < sizeof($resolution); $x++) {
- $url = 'https://img.youtube.com/vi/' . $id . '/' . $resolution[$x] . '.jpg';
- // Make sure we get a 200 OK HTTP response
- if (strpos(get_headers($url)[0], '200 OK') !== false) {
- break;
- }
- }
- return $url;
- }
-
}
"resource-carousel": [
"./scripts/resource-carousel.js"
],
+ "multimedia-carousel": [
+ "./scripts/multimedia-carousel.js"
+ ],
"modal-list": [
"./scripts/modal-list.js"
],
--- /dev/null
+// ELEMENTOR Trigger
+(function($) {
+ // Trigger handler when element ready
+ $(window).on( 'elementor/frontend/init', function() {
+ // eslint-disable-next-line
+ elementorFrontend.hooks.addAction('frontend/element_ready/cube-multimedia-carousel.default', function ($scope) {
+
+ $scope.find('.multimedia-carousel').slick(); // Note: settings come from data-attribute in HTML
+
+ });
+ });
+})(jQuery);
text-align: center
margin: 0 20px
+ &-link
+ display: block
+ outline: none
+
&-image
display: block
width: 100%
- max-width: 195px
+ max-width: 240px
margin: 0 auto 1.75em
background-size: cover
background-position: center
background-repeat: no-repeat
border-radius: 50%
- .profile-carousel-item:focus &
- border: 2px solid $colors.headings
+ //.profile-carousel-link:focus &
+ // box-shadow: inset 0 0 0 2px $colors.headings
&-sizer
padding-bottom: 100% // Make a square
--- /dev/null
+{{--MULTIMEDIA CAROUSEL--}}
+@php
+ $settings = [
+ 'slidesToShow' => 4,
+ 'slidesToScroll' => 4,
+ 'dots' => count($items) > 4, // Only show dots when there are enough items
+ 'infinite' => true,
+ 'responsive' => [
+ [
+ 'breakpoint' => 1300,
+ 'settings' => [
+ 'slidesToShow' => 3,
+ 'slidesToScroll' => 3,
+ 'dots' => count($items) > 3,
+ ]
+ ],
+ [
+ 'breakpoint' => 850,
+ 'settings' => [
+ 'slidesToShow' => 2,
+ 'slidesToScroll' => 2,
+ 'dots' => count($items) > 2,
+ ]
+ ],
+ [
+ 'breakpoint' => 650,
+ 'settings' => [
+ 'slidesToShow' => 1,
+ 'slidesToScroll' => 1,
+ 'dots' => count($items) > 1,
+ ]
+ ],
+ ]
+ ];
+
+ $slick = json_encode($settings);
+
+@endphp
+
+<div class="multimedia-carousel elementor-slick-slider" data-slick="{{ $slick }}">
+
+ @foreach ($items as $item)
+ @includeWhen($item['type'] == 'text', 'widgets/partials/profile-slide')
+ @includeWhen($item['type'] == 'video', 'widgets/partials/video-slide', ['video' => $item])
+ @endforeach
+
+</div>
--- /dev/null
+@php
+ $lightbox_ID = 'lightbox_'. $item['_id']; // Unique ID for lightbox content
+@endphp
+
+<div class="profile-carousel-item slick-slide">
+
+ <a href="#{{ $lightbox_ID }}" data-lity class="profile-carousel-link">
+
+ <div class="profile-carousel-image" style="background-image:url('{{ wp_get_attachment_image_url($item['image']['id'], 'post-thumbnail') }}')">
+ <div class="profile-carousel-image-sizer"></div>
+ </div>
+
+ <h3 class="profile-carousel-title">{!! str_replace('-', '‑', $item['title']) /* make hyphens non-breaking */ !!}</h3>
+
+ @if ($item['subtitle'])
+ <h4 class="profile-carousel-subtitle">{{ $item['subtitle'] }}</h4>
+ @endif
+
+ </a> {{-- .profile-carousel-item --}}
+
+ {{-- Lightbox Content --}}
+ <div id="{{ $lightbox_ID }}" class="lity-hide profile-carousel-lightbox">
+
+ <div class="profile-carousel-image" style="background-image:url('{{ wp_get_attachment_image_url($item['image']['id'], 'post-thumbnail') }}')">
+ <div class="profile-carousel-image-sizer"></div>
+ </div>
+
+ <h3 class="profile-carousel-title">{{ $item['title'] }}</h3>
+
+ <div class="profile-carousel-body">
+ {!! $item['body'] !!}
+ </div>
+ </div>
+
+</div>
--- /dev/null
+<div class="video-carousel-item slick-slide">
+ <div class="slick-slide-inner" data-elementor-lightbox="{{ $video['lightbox'] }}" data-elementor-open-lightbox="yes">
+ <div class="video-carousel-item-image" style="background-image:url('{{ $video['preview'] }}')">
+ <div class="video-carousel-item-image-sizer"></div>
+ </div>
+ <h4 class="video-carousel-item-title">{{ $video['title'] }}</h4>
+ <p class="video-carousel-item-details">{{ $video['details'] }}</p>
+ </div>
+</div>
<div class="profile-carousel elementor-slick-slider" data-slick="{{ $slick }}">
@foreach ($items as $item)
-
- @php
- $lightbox_ID = 'lightbox_'. $item['_id']; // Unique ID for lightbox content
- @endphp
-
- <a href="#{{ $lightbox_ID }}" data-lity class="profile-carousel-item slick-slide">
-
- <div class="profile-carousel-image" style="background-image:url('{{ wp_get_attachment_image_url($item['image']['id'], 'post-thumbnail') }}')">
- <div class="profile-carousel-image-sizer"></div>
- </div>
-
- <h3 class="profile-carousel-title">{!! str_replace('-', '‑', $item['title']) /* make hyphens non-breaking */ !!}</h3>
-
- @if ($item['subtitle'])
- <h4 class="profile-carousel-subtitle">{{ $item['subtitle'] }}</h4>
- @endif
-
- {{-- Lightbox Content --}}
- <div id="{{ $lightbox_ID }}" class="lity-hide profile-carousel-lightbox">
-
- <div class="profile-carousel-image" style="background-image:url('{{ wp_get_attachment_image_url($item['image']['id'], 'post-thumbnail') }}')">
- <div class="profile-carousel-image-sizer"></div>
- </div>
-
- <h3 class="profile-carousel-title">{{ $item['title'] }}</h3>
-
- <div class="profile-carousel-body">
- {!! $item['body'] !!}
- </div>
-
- </div>
-
- </a> {{-- .profile-carousel-item --}}
-
+ @include('widgets/partials/profile-slide')
@endforeach
</div>
<div class="video-carousel elementor-slick-slider" data-slick="{{ $slick }}">
@foreach ($videos as $video)
- <div class="video-carousel-item slick-slide">
- <div class="slick-slide-inner" data-elementor-lightbox="{{ $video['lightbox'] }}" data-elementor-open-lightbox="yes">
- <div class="video-carousel-item-image" style="background-image:url('{{ $video['preview'] }}')">
- <div class="video-carousel-item-image-sizer"></div>
- </div>
- <h4 class="video-carousel-item-title">{{ $video['title'] }}</h4>
- <p class="video-carousel-item-details">{{ $video['details'] }}</p>
- </div>
- </div>
+ @include('widgets/partials/video-slide')
@endforeach
</div>