Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions components/video/Component.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\View\Components;

use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;

class Video extends Component
{
public string $thumb;
public string $videoId;
public ?string $caption = null;

/**
* Create a new component instance.
*/
public function __construct(string $thumb, string $videoId, string $caption = null)
{
$this->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');
}
}
72 changes: 72 additions & 0 deletions components/video/Layout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Layouts;

use Hiker\Cms\Layouts\BaseLayout;
use Hiker\Components\DataList\DataList;
use Hiker\Components\Editor\Step;
use Hiker\Components\Fields\Image\Image as HikerImage;
use Hiker\Components\Fields\Text\Text;
use Hiker\Components\Image\Image as ImageComponent;
use Hiker\Components\Text\Text as TextComponent;
use Hiker\Tracks\Baggage;
use Illuminate\Support\Facades\Storage;

class Video extends BaseLayout
{
/**
* The label of the layout
*/
public function label(): string
{
return 'Video';
}

public function view(): string
{
return 'video';
}

/**
* The list of steps to display in the form
*/
public function form(Baggage $bag): array
{
return [
Step::make(static::label(), 'edit_video_layout')
->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']);
}
}
69 changes: 69 additions & 0 deletions components/video/js.js
Original file line number Diff line number Diff line change
@@ -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;
59 changes: 59 additions & 0 deletions components/video/style.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
15 changes: 15 additions & 0 deletions components/video/view.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="video" data-video-id={{ $videoId }}>
<div class="video__container">
<div class="video__overlay-container">
<div class="video__overlay" style="background-image: url({{ $thumb }})">
<x-button icon="play" view="icon-button">Click me</x-button>
</div>
</div>
<div class="video__iframe-container">
<div class="video__iframe" id="player"></div>
</div>
</div>
@if($caption)
<p class="video__caption">{{ $caption }}</p>
@endif
</div>
65 changes: 65 additions & 0 deletions src/Components/Publishers/Video.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace Whitecube\LaravelPreset\Components\Publishers;

use Whitecube\LaravelPreset\Components\File;
use Whitecube\LaravelPreset\Components\FilesCollection;
use Whitecube\LaravelPreset\Components\PublisherInterface;

class Video implements PublisherInterface
{
/**
* Get the component's displayable name.
*/
public function label(): string
{
return 'Video';
}

/**
* Let the publisher prompt for eventual extra input
* and return a collection of publishable files.
*/
public function handle(): FilesCollection
{
$style = File::makeFromStub(
stub: 'components/video/style.scss',
destination: resource_path('sass/parts/_video.scss'),
);

$js = File::makeFromStub(
stub: 'components/video/js.js',
destination: resource_path('js/parts/video.js'),
);
$view = File::makeFromStub(
stub: 'components/video/view.blade.php',
destination: resource_path('views/components/video.blade.php'),
);

$component = File::makeFromStub(
stub: 'components/video/Component.php',
destination: base_path('app/View/Components/Video.php'),
);

$layout = File::makeFromStub(
stub: 'components/video/Layout.php',
destination: base_path('app/Layout/Video.php'),
);

return FilesCollection::make([
$style,
$js,
$view,
$component,
$layout
]);
}

/**
* Get the component's usage instructions
*/
public function instructions(): ?string
{
return "1. Add `@import 'parts/video';` to `resources/sass/app.scss`\r\n2. Use the blade component: `<x-video videoId=\"A6AxD9bUk1o\" :image=\"asset('img/money.png')\" caption=\"the money these components will give us !!!\" />`";
}
}