use App\Models\Settings;
use Cubist\Backpack\Magic\PageData;
use Cubist\Util\Files\Files;
+use FFMpeg\FFProbe;
+use SplFileInfo;
class FrontController extends Controller
{
protected function _getDataFromCMS()
{
- return ['menu' => $this->_getMenuFromCMS(),
+ $res = ['menu' => $this->_getMenuFromCMS(),
'resources' => $this->_getResourcesFromCMS(),
'home' => $this->_getHomeFromCMS(),
'medialibrary' => $this->_getMediaLibraryFromCMS(),
'resources_pdf' => $this->_getResourcesPDFFromCMS(),
'settings' => $this->_getSettingsFromCMS()];
+
+ $theme_order = [1, 5, 2, 6, 3, 7, 4, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+
+ $themes = [];
+ foreach ($theme_order as $id) {
+ if (isset($res['medialibrary']['themes'][$id])) {
+ $themes[$id] = $res['medialibrary']['themes'][$id];
+ }
+ }
+ $res['theme_order'] = $theme_order;
+ $res['themes'] = $themes;
+ $res['media_types'] = [
+ 'video' => 'Vidéos',
+ 'audio' => 'Podcast',
+ ];
+
+ $media=[];
+ $ffprobe = FFProbe::create();
+ foreach ($res['medialibrary']['assets'] as $a) {
+ /** @var $a \Cubist\Backpack\Magic\PageData */
+ $file=$a->getMediaPathsByCollection($a->get('file_upload'))[0]??null;
+ if(null===$file){
+ continue;
+ }
+
+ $spf=new SplFileInfo($file);
+ $ext=mb_strtolower($spf->getExtension());
+ if(in_array($ext,['pdf','jpg','jpeg','gif','png'])){
+ continue;
+ }
+ $map=['mp3'=>'audio','mp4'=>'video'];
+ $type=$map[$ext]??$a->get('type');
+
+ $media[$a['id']]=[
+ 'id'=>$a->get('id'),
+ 'type'=>$type,
+ 'theme'=>$a->get('theme'),
+ 'code'=>$a->get('code'),
+ 'title'=>$a->get('title'),
+ 'file'=>$a->getImageURLbyCollection($a->get('file_upload')),
+ 'image'=>$a->getImageURLbyCollection($a->get('file_thumb'))??$a->getImageURLbyCollection($a->get('file_upload'),'poster'),
+ 'duration'=>$ffprobe->format($file)->get('duration')
+ ];
+ }
+ $res['media']=$media;
+
+ return $res;
}
protected function _getMenuFromCMS()
use App\Models\Asset;
use App\Models\Publication;
+use Cubist\Backpack\Magic\PageData;
use Cubist\Util\Files\Files;
+use Cubist\Util\Text;
use Cubist\Util\Zip;
use Fluidbook\Tools\Compiler\Compiler;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
$this->addSource($media->getPath());
/** @var Media $precompiled */
- $precompiled = $this->pub->getMediaInField($this->pub->getAttributeValue('precompiled'))->first()->getPath();
+ $precompiled = resource_path('precompiled.zip');
$stubuid = md5($precompiled . '//' . filemtime($precompiled) . '///' . filesize($precompiled));
$this->stub = storage_path('fluidbook/stubs/' . $stubuid . '/');
if (!file_exists($this->stub)) {
$this->config->mediaIDToFile = [];
$res = [];
+ // Add media
+ $assets = PageData::fromEntities(Asset::getAssetsInMediaLibrary());
+ foreach ($assets as $asset) {
+ $links[] = ['page' => 'background', 'to' => 'video-popup:' . $asset->id, 'left' => -1, 'top' => -1, 'width' => 1, 'height' => 1, 'uid' => 'media_' . $asset->id];
+ }
+
foreach ($links as $k => $link) {
$e = explode(':', $link['to']);
- $ee = explode('/', $e[0]);
+ $ee = Text::multiExplode('/-', $e[0]);
if ($ee[0] == 'anim') {
- $base = [['type'=>'','ease' => 'Power2.easeOut', 'duration' => 1.5, 'transformOrigin' => 'center', 'delay' => 1], []];
+ $base = [['type' => '', 'ease' => 'Power2.easeOut', 'duration' => 1.5, 'transformOrigin' => 'center', 'delay' => 1], []];
switch ($ee[1]) {
case 'fadein':
$link['type'] = 14;
$link['loop'] = false;
$link['sound'] = true;
}
- } else if (stripos($e[0], 'slideshow/') === 0) {
+ } else if ($ee[0] === 'slideshow') {
$link['inline'] = $ee[1] === 'inline';
$link['to'] = $this->_zipAssets($e[1]);
if ($link['inline']) {
parent::setFields();
$this->addField('document', Files::class, 'Document', ['tab' => 'Publication interactive']);
- $this->addField('precompiled', Files::class, 'Fluidbook Précompilé', ['can' => 'precompiled', 'tab' => 'Publication interactive']);
- $this->addField('animdemo', Files::class, 'Fluidbook Démo', ['can' => 'precompiled', 'tab' => 'Publication interactive']);
$this->addField('logo', Images::class, 'Logo', ['tab' => 'Contenus']);
$this->addField('subtitle', Text::class, 'Sous-titre', ['tab' => 'Contenus']);
$this->addField('illustration', Images::class, 'Image', ['tab' => 'Contenus']);
case "tel:":
case "pdf:":
case "tooltip:":
+ case "video/popup:":
+ case "video/inline:":
+ case "video-popup:":
+ case "video-inline:":
return true;
default:
case "tel:":
case "pdf:":
case "tooltip:":
+ case "video/popup:":
+ case "video/inline:":
+ case "video-popup:":
+ case "video-inline:":
return true;
default:
return true;
});
+ $(document).on('click', 'a[href^="video"]', function () {
+ var e = $(this).attr('href').split(':');
+ var videoid = e[1];
+
+ if (inFluidbook) {
+ window.parent.fluidbook.links.triggerLinkById('media_' + videoid);
+ } else {
+ window.parent.openVideo(videoid);
+ }
+ return false;
+ });
+
$(document).on('click', 'a[href^="pdf:"]', function () {
var e = $(this).attr('href').split('#');
var file;
{{-- MEDIA LIBRARY GRID --}}
{{-- Negative margins applied here to offset margins used in Isotope grid --}}
<div class="media-grid opacity-0 mt-10 -mb-16 -mx-2.5" x-init="initIsotope($el)">
- @foreach ($media as $item)
-
- @php
- $filters = [];
- if (!is_array($item['theme'])) {
- $item['theme'] = [$item['theme']];
- }
- foreach ($item['theme'] as $t) {
- $filters[] = 'theme-'.$t;
- }
- $theme = $themes[$item['theme'][0]];
- $player_data = [
- 'ID' => $item['id'],
- 'player' => [
- 'type' => $item['type'], // video / audio
- 'sources' => [
- [
- 'src' => $item['file'],
- ]
- ]
- ],
- 'title' => $item['title'],
- 'image' => $item['image'],
- ];
- @endphp
-
- <div id="media_{{ $item['id'] }}"
- @click="$dispatch('player-open', { sourceData: JSON.parse($el.dataset.player) })"
- data-player='{{ json_encode($player_data) }}'
- class="media-item group cursor-pointer
- {{-- Width is 25% minus the gutters (2 * 0.625rem that comes from mx-2.5) --}}
- float-left w-[calc(100%/2-1.25rem)] md:w-[calc(100%/3-1.25rem)] lg:w-[calc(100%/4-1.25rem)] mx-2.5 mb-16
- {{ implode(' ',$filters) }}
- ">
- <div class=" media-item-image relative bg-cover bg-no-repeat rounded-md pb-[56.25%]"
- style="background-color:#ddd;background-image:url({{ $item['image'] }})">
-
- {{-- Play Icon --}}
- <svg
- class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 transition group-hover:scale-110"
- xmlns="http://www.w3.org/2000/svg" width="22.47%" viewBox="0 0 59.999 59.999">
- {{-- Icon BG --}}
- <path d="M0 29.999a30 30 0 1 0 30-30 30 30 0 0 0-30 30Z" fill="#0725e2"/>
-
- <g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
- @if ($item['type'] === 'audio')
- {{-- Audio Play Icon --}}
- <path d="M15.999 25.324h5.324v9.317h-5.324Z"/>
- <path d="M35.321 24.635a7.561 7.561 0 0 1 0 10.693 7.523 7.523 0 0 0 0-10.693Z"/>
- <path d="M39.644 21.627a11.815 11.815 0 0 1 0 16.709 11.756 11.756 0 0 0 0-16.709Z"/>
- <path d="M21.323 25.324 30.64 20v19.966l-9.317-5.325Z"/>
- @else
- {{-- Video Play Icon --}}
- <path d="m26.5 38.5 12-8-12-8Z"/>
- @endif
- </g>
- </svg>
-
- <x-duration class="absolute bottom-2 right-2 font-secondary text-white"
- style="text-shadow: rgb(0 0 0 / 50%) 0 0.1em 0.2em">
- {{ $item['duration'] }}
- </x-duration>
- </div>
- {{-- THEME LABEL --}}
- <div class="mt-2.5 font-secondary font-medium text-xs leading-none" style="color:{{ $theme['color'] }}">
- {{ $theme['name'] }}
- </div>
- {{-- Set min height of 2 lines so intial float layout is less likely to have misplaced blocks --}}
- <div class="mt-1.5 font-semibold leading-snug min-h-[2.75em]">
- ({{$item['code']}}) {{ $item['title'] }}
- </div>
- </div>
- @endforeach
+ @include('front.media')
</div>
<x-media-library.filter-interface/>
@extends('layouts.app')
@section('content')
-
- @php
- $media_types = [
- 'video' => 'Vidéos',
- 'audio' => 'Podcast',
- ];
-
- $theme_order=[1,5,2,6,3,7,4,8,9,10,11,12,13,14,15,16];
-
- $themes=[];
- foreach ($theme_order as $id){
- if(isset($medialibrary['themes'][$id])){
- $themes[$id]=$medialibrary['themes'][$id];
- }
- }
-
- $media=[];
- $ffprobe = FFMpeg\FFProbe::create();
- foreach ($medialibrary['assets'] as $a) {
- /** @var $a \Cubist\Backpack\Magic\PageData */
- $file=$a->getMediaPathsByCollection($a->get('file_upload'))[0]??null;
- if(null===$file){
- continue;
- }
-
- $spf=new SplFileInfo($file);
- $ext=mb_strtolower($spf->getExtension());
- if(in_array($ext,['pdf','jpg','jpeg','gif','png'])){
- continue;
- }
- $map=['mp3'=>'audio','mp4'=>'video'];
- $type=$map[$ext]??$a->get('type');
-
- $media[$a['id']]=[
- 'id'=>$a->get('id'),
- 'type'=>$type,
- 'theme'=>$a->get('theme'),
- 'code'=>$a->get('code'),
- 'title'=>$a->get('title'),
- 'file'=>$a->getImageURLbyCollection($a->get('file_upload')),
- 'image'=>$a->getImageURLbyCollection($a->get('file_thumb'))??$a->getImageURLbyCollection($a->get('file_upload'),'poster'),
- 'duration'=>$ffprobe->format($file)->get('duration')
- ];
- }
- @endphp
-
<div class="overflow-hidden py-2">{{-- vertical padding so accents don't get cropped --}}
<h1 class="uppercase animate animate-in-up"
style="--animation-duration: 0.7s;">
--- /dev/null
+@foreach ($media as $item)
+
+ @php
+ $filters = [];
+ if (!is_array($item['theme'])) {
+ $item['theme'] = [$item['theme']];
+ }
+ foreach ($item['theme'] as $t) {
+ $filters[] = 'theme-'.$t;
+ }
+ $theme = $themes[$item['theme'][0]];
+ $player_data = [
+ 'ID' => $item['id'],
+ 'player' => [
+ 'type' => $item['type'], // video / audio
+ 'sources' => [
+ [
+ 'src' => $item['file'],
+ ]
+ ]
+ ],
+ 'title' => $item['title'],
+ 'image' => $item['image'],
+ ];
+ @endphp
+
+ <div id="media_{{ $item['id'] }}"
+ @click="$dispatch('player-open', { sourceData: JSON.parse($el.dataset.player) })"
+ data-player='{{ json_encode($player_data) }}'
+ class="media-item group cursor-pointer
+ {{-- Width is 25% minus the gutters (2 * 0.625rem that comes from mx-2.5) --}}
+ float-left w-[calc(100%/2-1.25rem)] md:w-[calc(100%/3-1.25rem)] lg:w-[calc(100%/4-1.25rem)] mx-2.5 mb-16
+{{ implode(' ',$filters) }}
+ ">
+ <div class=" media-item-image relative bg-cover bg-no-repeat rounded-md pb-[56.25%]"
+ style="background-color:#ddd;background-image:url({{ $item['image'] }})">
+
+ {{-- Play Icon --}}
+ <svg
+ class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 transition group-hover:scale-110"
+ xmlns="http://www.w3.org/2000/svg" width="22.47%" viewBox="0 0 59.999 59.999">
+ {{-- Icon BG --}}
+ <path d="M0 29.999a30 30 0 1 0 30-30 30 30 0 0 0-30 30Z" fill="#0725e2"/>
+
+ <g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+ @if ($item['type'] === 'audio')
+ {{-- Audio Play Icon --}}
+ <path d="M15.999 25.324h5.324v9.317h-5.324Z"/>
+ <path d="M35.321 24.635a7.561 7.561 0 0 1 0 10.693 7.523 7.523 0 0 0 0-10.693Z"/>
+ <path d="M39.644 21.627a11.815 11.815 0 0 1 0 16.709 11.756 11.756 0 0 0 0-16.709Z"/>
+ <path d="M21.323 25.324 30.64 20v19.966l-9.317-5.325Z"/>
+ @else
+ {{-- Video Play Icon --}}
+ <path d="m26.5 38.5 12-8-12-8Z"/>
+ @endif
+ </g>
+ </svg>
+
+ <x-duration class="absolute bottom-2 right-2 font-secondary text-white"
+ style="text-shadow: rgb(0 0 0 / 50%) 0 0.1em 0.2em">
+ {{ $item['duration'] }}
+ </x-duration>
+ </div>
+ {{-- THEME LABEL --}}
+ <div class="mt-2.5 font-secondary font-medium text-xs leading-none" style="color:{{ $theme['color'] }}">
+ {{ $theme['name'] }}
+ </div>
+ {{-- Set min height of 2 lines so intial float layout is less likely to have misplaced blocks --}}
+ <div class="mt-1.5 font-semibold leading-snug min-h-[2.75em]">
+ ({{$item['code']}}) {{ $item['title'] }}
+ </div>
+ </div>
+@endforeach
},
}
}
+
+ function simulateClick(elem) {
+ // Create our event (with options)
+ var evt = new MouseEvent('click', {
+ bubbles: true,
+ cancelable: true,
+ view: window
+ });
+ // If cancelled, don't dispatch our event
+ var canceled = !elem.dispatchEvent(evt);
+ }
+
+ function openVideo(id) {
+ simulateClick(document.getElementById('media_' + id));
+ }
</script>
@endpush
<body x-data="app()"
@resize.window.debounce="measureWidth()"
class="font-primary p-8 @stack('extra_body_classes')">
-@endsection
+ @endsection
-@section('main')
- {{--
- Content has constrained width so that nothing shifts when overlay is opened and scrolling is disabled.
- The body width changes when scrollbars are removed, which causes alignment problems, especially with
- fixed-position elements.
- --}}
- <div style="width: var(--content-width)">
- <x-header main="true"/>
-
- <div class="min-h-content flex flex-col mx-22 pt-22 opacity-0" :class="{ 'opacity-0': maskContents }">
- @yield('content')
+ @section('main')
+ <div id="mediahidden" style="display: none">
+ @include('front.media')
+ </div>
+ {{--
+ Content has constrained width so that nothing shifts when overlay is opened and scrolling is disabled.
+ The body width changes when scrollbars are removed, which causes alignment problems, especially with
+ fixed-position elements.
+ --}}
+ <div style="width: var(--content-width)">
+ <x-header main="true"/>
+
+ <div class="min-h-content flex flex-col mx-22 pt-22 opacity-0" :class="{ 'opacity-0': maskContents }">
+ @yield('content')
+ </div>
</div>
- </div>
- {{-- MENU OVERLAY --}}
- <div class="overlay menu-overlay
+ {{-- MENU OVERLAY --}}
+ <div class="overlay menu-overlay
bg-blue text-white
z-30"
- x-show="menuOpen"
- x-transition.opacity.duration.500ms
- x-transition:leave.opacity.duration.500ms.delay.500ms
- x-cloak>
+ x-show="menuOpen"
+ x-transition.opacity.duration.500ms
+ x-transition:leave.opacity.duration.500ms.delay.500ms
+ x-cloak>
- <x-header logo-class="text-white"/>
+ <x-header logo-class="text-white"/>
- <ul class="w-full font-medium text-6xl">
- @foreach ($menu as $link)
+ <ul class="w-full font-medium text-6xl">
+ @foreach ($menu as $link)
- @php
- $prevent='menuOpen = false; setTimeout(() => window.location = $event.target.href, 200);';
- if($link['type']==='pdf'){
- $prevent='menuOpen = false;openPDF($el.attributes.href.value)';
- }
- @endphp
- {{-- Overflow is hidden for text entrance animation. --}}
- {{-- Extra padding and negative margin added so hover scale effect isn't clipped --}}
- <li class="overflow-hidden pl-4 -ml-4">
- <div x-show="menuOpen"
- class="transition transform ease-out-quint"
- style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
- x-transition:enter="duration-1000"
- x-transition:enter-start="translate-y-[100px]"
- x-transition:enter-end="translate-x-0"
- x-transition:leave="duration-500"
- x-transition:leave-start="translate-x-0"
- x-transition:leave-end="translate-y-[100px]">
- <x-link
- href="{{ $link['url'] }}"
- @click.prevent="{!! $prevent !!}"
- class="block py-8 text-current
+ @php
+ $prevent='menuOpen = false; setTimeout(() => window.location = $event.target.href, 200);';
+ if($link['type']==='pdf'){
+ $prevent='menuOpen = false;openPDF($el.attributes.href.value)';
+ }
+ @endphp
+ {{-- Overflow is hidden for text entrance animation. --}}
+ {{-- Extra padding and negative margin added so hover scale effect isn't clipped --}}
+ <li class="overflow-hidden pl-4 -ml-4">
+ <div x-show="menuOpen"
+ class="transition transform ease-out-quint"
+ style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
+ x-transition:enter="duration-1000"
+ x-transition:enter-start="translate-y-[100px]"
+ x-transition:enter-end="translate-x-0"
+ x-transition:leave="duration-500"
+ x-transition:leave-start="translate-x-0"
+ x-transition:leave-end="translate-y-[100px]">
+ <x-link
+ href="{{ $link['url'] }}"
+ @click.prevent="{!! $prevent !!}"
+ class="block py-8 text-current
transform origin-bottom-left
transition-transform duration-200
hover:scale-105">
- {{ $link['title'] }}
- </x-link>
- </div>
- </li>
- <li class="bg-blue-dark h-px
+ {{ $link['title'] }}
+ </x-link>
+ </div>
+ </li>
+ <li class="bg-blue-dark h-px
transform origin-left
transition ease-out-quint"
- x-show="menuOpen"
- style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
- x-transition:enter="duration-2000"
- x-transition:enter-start="scale-x-0"
- x-transition:enter-end="scale-x-100"
- x-transition:leave="duration-500"
- x-transition:leave-start="scale-x-100"
- x-transition:leave-end="scale-x-0">
- </li>
- @endforeach
- </ul>
- </div>
-
- {{-- PDF Viewer Overlay --}}
- <div class="overlay pdf-overlay
+ x-show="menuOpen"
+ style="transition-delay: {{ 250 + (50 * $loop->index) }}ms"
+ x-transition:enter="duration-2000"
+ x-transition:enter-start="scale-x-0"
+ x-transition:enter-end="scale-x-100"
+ x-transition:leave="duration-500"
+ x-transition:leave-start="scale-x-100"
+ x-transition:leave-end="scale-x-0">
+ </li>
+ @endforeach
+ </ul>
+ </div>
+
+ {{-- PDF Viewer Overlay --}}
+ <div class="overlay pdf-overlay
bg-white text-black
pt-20
z-20"
- x-show="PDFOpen"
- x-transition:enter.opacity.duration.500ms
- x-transition:leave.opacity.duration.200ms
- x-cloak>
-
- {{-- PDF Viewer iframe --}}
- <iframe x-ref="PDFViewer" src="" frameborder="0"
- class="absolute left-0 w-full top-[90px] h-[calc(100vh-90px)]"></iframe>
-
- <x-header>
- <x-slot name="right">
- <div x-show="PDFOpen && !menuOpen" x-transition:enter.opacity.duration.500ms.delay.300ms
- x-transition:leave.opacity.duration.0ms.delay.0ms>
- <x-close
- x-show="PDFOpen"
- @click.prevent="closePDF()"
- class="flex"
- />
- </div>
- </x-slot>
- </x-header>
- </div>
-
- {{-- Media Player --}}
- <x-media-library.player/>
-
- {{-- Search Overlay (placed last so it sits above other modals, like the PDF viewer, with the same z-index) --}}
- <x-search/>
+ x-show="PDFOpen"
+ x-transition:enter.opacity.duration.500ms
+ x-transition:leave.opacity.duration.200ms
+ x-cloak>
+
+ {{-- PDF Viewer iframe --}}
+ <iframe x-ref="PDFViewer" src="" frameborder="0"
+ class="absolute left-0 w-full top-[90px] h-[calc(100vh-90px)]"></iframe>
+
+ <x-header>
+ <x-slot name="right">
+ <div x-show="PDFOpen && !menuOpen" x-transition:enter.opacity.duration.500ms.delay.300ms
+ x-transition:leave.opacity.duration.0ms.delay.0ms>
+ <x-close
+ x-show="PDFOpen"
+ @click.prevent="closePDF()"
+ class="flex"
+ />
+ </div>
+ </x-slot>
+ </x-header>
+ </div>
+
+ {{-- Media Player --}}
+ <x-media-library.player/>
+
+ {{-- Search Overlay (placed last so it sits above other modals, like the PDF viewer, with the same z-index) --}}
+ <x-search/>
@endsection