diff --git a/components/video/Component.php b/components/video/Component.php new file mode 100644 index 0000000..3184eb9 --- /dev/null +++ b/components/video/Component.php @@ -0,0 +1,32 @@ +thumb = $thumb; + $this->videoId = $videoId; + $this->caption = $caption; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View|Closure|string + { + return view('components.video'); + } +} diff --git a/components/video/Layout.php b/components/video/Layout.php new file mode 100644 index 0000000..9e246e5 --- /dev/null +++ b/components/video/Layout.php @@ -0,0 +1,72 @@ +fields([ + HikerImage::make('Cover image', 'thumb') + ->rules('required') + ->disk('public'), + + Text::make('YouTube identifier', 'videoId') + ->rules('required') + ->help('The YouTube identifier is the set of characters following the "watch?v=" in the video url. Ex: in the link https://www.youtube.com/watch?v=A6AxD9bUk1o, the identifier is A6AxD9bUk1o.'), + + Text::make('Caption', 'caption') + ->help('Optionnal.'), + ]), + ]; + } + + /** + * The layout's display components + */ + public function display(): array + { + return [ + DataList::make() + ->row('Cover image', ImageComponent::make(Storage::url($this->thumb))->aspectRatio('16/9')) + ->row('YouTube identifier', TextComponent::make($this->videoId)) + ->row('Caption', TextComponent::make($this->caption)) + ]; + } + + /** + * Extract the values from the bag to store them in the database. + */ + public function fillAttributes(Baggage $bag): array + { + return $bag->only(['thumb', 'caption', 'videoId']); + } +} diff --git a/components/video/js.js b/components/video/js.js new file mode 100644 index 0000000..28f19f6 --- /dev/null +++ b/components/video/js.js @@ -0,0 +1,69 @@ +export default class Video { + static selector = ".video"; + + constructor(el) { + this.el = el; + this.getElements(); + this.setEvents(); + this.player = null; + this.playerReady = false; + + this.initIframe(); + } + + getElements() { + this.overlay = this.el.querySelector(".video__overlay-container"); + this.playButton = this.el.querySelector(".video .icon-button"); + this.videoIframe = this.el.querySelector(".video__iframe"); + } + + setEvents() { + this.playButton.addEventListener("click", (e) => { + e.preventDefault(); + this.toggleVideo(); + }); + } + + initIframe() { + const tag = document.createElement("script"); + tag.src = "https://www.youtube.com/iframe_api"; + const firstScriptTag = document.getElementsByTagName("script")[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + } + + loadIframe() { + this.player = new YT.Player("player", { + height: "360", + width: "640", + videoId: this.el.dataset.videoId, + events: { + onReady: this.onPlayerReady, + onStateChange: this.onPlayerStateChange, + }, + }); + } + + toggleVideo() { + if (!this.player || !this.playerReady) return; + + this.overlay.classList.add("video__overlay-container--playing"); + + this.player.playVideo(); + } + + onPlayerReady = (event) => { + this.playerReady = true; + }; + + onPlayerStateChange = (event) => { + if (event.data === YT.PlayerState.ENDED) { + this.overlay.classList.remove("video__overlay-container--playing"); + } + }; +} + +function onYouTubeIframeAPIReady() { + window.app.pluton.call(".video", "loadIframe"); +} + +window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady; diff --git a/components/video/style.scss b/components/video/style.scss new file mode 100644 index 0000000..d181490 --- /dev/null +++ b/components/video/style.scss @@ -0,0 +1,59 @@ +.video { + margin: rem(32) auto; + width: col(6, 12); + + &__container { + position: relative; + } + + &__iframe-container { + @include respVideoContainer(12, 12); + } + + &__overlay-container { + position: absolute; + z-index: 10; + width: 100%; + padding-bottom: 56.25%; + height: 0; + transition: opacity 1s; + visibility: visible; + opacity: 1; + + &--playing { + opacity: 0; + transition: opacity 1s, visibility 0s 1s; + visibility: hidden; + } + } + + &__overlay, + &__btn { + position: absolute; + display: grid; + align-items: center; + justify-items: center; + } + + &__overlay { + background-size: cover; + background-position: center; + width: 100%; + height: 100%; + @include cover(); + } + + &__caption { + font-size: rem(12); + line-height: 130%; + margin-top: rem(12); + } + + @include mq($until: l) { + width: col(10, 12); + } + + @include mq($until: s) { + width: auto; + } +} diff --git a/components/video/view.blade.php b/components/video/view.blade.php new file mode 100644 index 0000000..bda8b54 --- /dev/null +++ b/components/video/view.blade.php @@ -0,0 +1,15 @@ +
{{ $caption }}
+ @endif +