use App\Models\Product;
use App\Models\QuoteRequest;
use App\Models\Settings;
+use App\Models\News;
use Carbon\Carbon;
use Cubist\Backpack\app\Http\Controllers\CubistFrontController;
use Cubist\Backpack\app\Magic\PageData;
return Product::getFilteredProducts($product_type, $filter_values);
}
+
+ public function news(Request $request) {
+ // The current page number is passed in the HTTP request
+ // and detected automatically by Laravel's paginate() function
+ // Ref: https://laravel.com/docs/5.8/pagination#paginating-query-builder-results
+ //$page = $request->page ?? 1;
+ $per_page = $request->per_page ?? 8;
+ $exclude = $request->exclude ?? false;
+
+ $posts = News::getPosts($per_page, $exclude);
+
+ $news = [
+ 'total' => $posts->total(),
+ 'current_page' => $posts->currentPage(),
+ 'last_page' => $posts->lastPage(),
+ 'html' => '',
+ ];
+
+ foreach ($posts as $post) {
+ $news['html'] .= view('components.news-item', ['item' => $post]);
+ }
+
+ return $news;
+ }
}
}
$this->data['title'] = $newsItem->title;
- $this->data['image'] = $newsItem->getFirstMediaUrl($newsItem->image, 'details');
- $this->data['alt'] = $newsItem->getFirstMediaAlt($newsItem->image, $newsItem->title);
$this->data['news'] = $newsItem;
$this->data['page'] = $newsItem->getPageData();
namespace App\Models;
+use Carbon\Carbon;
use Spatie\Image\Manipulations;
-use Spatie\MediaLibrary\Conversion\Conversion;
use Spatie\MediaLibrary\Models\Media;
+use Cubist\Backpack\Facades\App;
+use Cubist\Backpack\app\Magic\Menu\Menu;
use Cubist\Backpack\app\Magic\Models\News as BaseNews;
class News extends BaseNews
{
public function registerMediaConversions(Media $media = null)
{
-
parent::registerMediaConversions($media);
- $this->addMediaConversion('details')
- ->width(600);
+ $this->addMediaConversion('featured')
+ ->width(768);
$this->addMediaConversion('index_thumb')
- ->crop(Manipulations::CROP_CENTER, 600, 400);
+ ->crop(Manipulations::CROP_CENTER, 348, 196);
}
- public function getThumbnailImage($class = '') {
+ public static function getPosts($per_page = 8, $exclude = null) {
+
+ $locale = App::getLocale();
- // Fallback replacement when there's no image
- $fallback = '<div class="'. $class .'"><div class="bg-grey-100" style="padding-bottom:66.67%"></div></div>';
+ $news = News::with('media')
+ ->whereVariant()
+ ->where('type', 'news')
+ ->where("status->$locale", 1)
+ ->whereDate('date', '<=', Carbon::now())
+ ->orderBy('date', 'desc');
+
+ if ($exclude) {
+ $news->where('id', '!=', intval($exclude));
+ }
+
+ return $news->paginate($per_page);
+ }
+
+ public function getThumbnailImage($class = '')
+ {
+ return $this->getImage('index_thumb', $class);
+ }
+
+ public function getImage($size = 'index_thumb', $class = '') {
+
+ // Fallback replacement when there's no image (based on thumbnail ratio of 196/348
+ $fallback = '<div class="'. $class .'"><div class="bg-grey-100" style="padding-bottom: 56.32%"></div></div>';
if (!$this->image) {
return $fallback;
}
- $src = $this->getFirstMediaUrl($this->image, 'index_thumb');
+ $src = $this->getFirstMediaUrl($this->image, $size);
$alt = e($this->getFirstMediaAlt($this->image, $this->title));
if (empty($src)) {
return '<img class="'. $class .'" src="'. $src .'" alt="'. $alt .'">';
}
+ public function getURL($id) {
+ $nav = Menu::getNavigation();
+ return $nav->getHrefByID("news/{$id}");
+ }
+
// Display compact date range in localised human readable format
// Ref: https://codereview.stackexchange.com/a/78303
// Alternative: https://github.com/flack/ranger
BladeX::component('components.phone-link'); // <phone-link number="012345"> ... </phone-link>
BladeX::component('components.text-block'); // <text-block> ... </text-block>
BladeX::component('components.cart-add')->tag('cart-add'); // <cart-add id="1234"> ... </cart-add>
+ BladeX::component('components.news-grid'); // <news-grid :items="$news"> ... </news-grid>
+ BladeX::component('components.news-item'); // <news-item :item="$newsItem"> ... </news-item>
} catch (\Exception $e) {
}
protected function _getNews()
{
-
- $newsItems = [];
-
- $news = News::with('media')
- ->whereVariant()
- ->where('type', 'news')
- ->orderBy('date', 'desc')
- ->get();
-
- foreach ($news as $newsItem) {
- if (!$newsItem->getPageData()->get('status')) {
- continue;
- }
- $newsItems[$newsItem->id] = $newsItem;
- }
-
- return array_slice($newsItems, 0, 4, true);
-
+ return News::getPosts(4);
}
return 'Actualités';
}
+ public function init()
+ {
+ parent::init();
+ $this->removeField('intro'); // Intro section not used on this page
+ }
+
public function setMenuChildren($menu)
{
Debugbar::startMeasure('nav_news', 'Make news nav items');
// Set extra data for News blade view
public function setData(&$data)
{
- $data['news'] = $this->_getNews();
- $data['events'] = $this->_getEvents();
+ $news = $this->_getNews();
+ $data['featured_news'] = $news->shift(); // Isolate first news item as feature
+ $data['news'] = $news; // The rest of the items will display in the grid
+ $data['more_news'] = $news->lastPage() > 1; // Is there any more news after this initial lot?
+
+ //$data['events'] = $this->_getEvents();
}
protected function _getNews()
{
-
- $newsItems = [];
-
- $news = NewsModel::with('media')
- ->whereVariant()
- ->where('type', 'news')
- ->whereDate('date', '<=', Carbon::now())
- ->orderBy('date', 'desc')
- ->get();
-
- foreach ($news as $newsItem) {
- if (!$newsItem->getPageData()->get('status')) {
- continue;
- }
- $newsItems[$newsItem->id] = $newsItem;
- }
-
- return $newsItems;
-
+ return NewsModel::getPosts(9); // Featured item + initial 8 items for grid
}
protected function _getEvents()
],
],
+ /* Grid overlay helper for layout */
+ 'grid_overlay' => isset($_GET['grid-overlay']) || env('GRID_OVERLAY', false),
+
/*
|--------------------------------------------------------------------------
| Application URL
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 26.273 26.273">
+ <g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
+ <path d="M15.829 10.982c0 .3-.442.539-.987.539H4.965c-.545 0-.987-.241-.987-.539V5.597c0-.3.442-.539.987-.539h9.873c.545 0 .987.241.987.539Z"/>
+ <path d="M3.982 14.752H15.83"/>
+ <path d="M3.982 17.983h7.971"/>
+ <path d="M22.292 25.522H3.982a3.231 3.231 0 0 1-3.231-3.231V1.827A1.077 1.077 0 0 1 1.828.75h16.156a1.077 1.077 0 0 1 1.077 1.077v20.464a3.231 3.231 0 0 0 6.462 0V3.981"/>
+ <path d="M22.292 3.981V22.83"/>
+ </g>
+</svg>
--- /dev/null
+<!-- Generic Component to fetch paged HTML results from an endpoint -->
+<template>
+ <button
+ v-text="label"
+ v-show="moreItems"
+ @click="loadItems">
+ </button>
+</template>
+
+<script>
+
+ export default {
+
+ name: 'fetch-items',
+
+ components: {},
+
+ data() {
+ return {
+ moreItems: true,
+ currentPage: this.page,
+ }
+ },
+
+ props: {
+ label: {
+ type: String,
+ },
+ endpoint: {
+ type: String,
+ required: true
+ },
+ page: {
+ default: 1
+ },
+ data: {
+ type: String,
+ default: '{}' // Any extra data to be passed to endpoint during post
+ },
+ target: {
+ type: String,
+ default: 'body'
+ }
+ },
+
+ mounted () {
+
+ },
+
+ methods: {
+
+ loadItems() {
+ let $this = this;
+
+ axios.post(this.endpoint, {
+ page: ++this.currentPage,
+ ...JSON.parse(this.data), // Have to parse data first because it comes as a string via attribute
+ })
+ .then(function(response) {
+ $this.appendItems(response.data);
+ })
+ .catch(function(error) {
+ console.error('Error fetching data', error);
+ });
+
+ },
+
+ appendItems(items) {
+ this.moreItems = this.currentPage < items.last_page;
+ document.querySelector(this.target).insertAdjacentHTML('beforeend', items.html);
+ },
+
+ }
+ }
+</script>
@apply text-navy font-display font-semibold
.h1, .h2, .markdown h1, .markdown h2
- @apply leading-tight relative
+ @apply leading-none relative
&:not(.simple):after
@apply bg-primary
@apply text-6xl
&:after
- constrain(margin-top, 1vw)
- constrain(margin-bottom, 2.5vw)
+ vertical-spacing(1.875vw, margin)
constrain(width, 10vw)
height: 8px
// Partial underline hover effect
a.partial-underline
- overflow: hidden
+ //overflow: hidden
&:before
constrain(width, 2.5vw)
// This will generate classes like pt-1v, pt-2v, pr-1v etc.
$vw-spacing = {
+ '0\.75': 1.875vw,
'1': 2.5vw,
+ '1\.25': 3.125vw,
'2': 5vw,
'3': 7.5vw,
'4': 10vw,
@php
// Default classes
$class = $class ?? '';
+ $id_attribute = empty($id) ? '' : 'id="'. $id .'"';
$cols = !empty($cols) ? 'grid-cols-'.$cols : 'grid-cols-auto';
$gap = isset($gap) ? 'grid-gap-'.$gap : 'grid-gap-lg'; // Can be overridden by passing an empty string
@endphp
-<div class="grid list {{ $cols }} {{ $gap }} {{ $class }}">
+<div class="grid list {{ $cols }} {{ $gap }} {{ $class }}" {!! $id_attribute !!}>
{{ $slot }}
</div>
--- /dev/null
+@php
+ $items = $items ?? [];
+ $id = $id ?? '';
+ $extraClass = $extraClass ?? '';
+ $class = 'md:grid-cols-2 xs:grid-cols-1 xs:grid-gap-20v ' . $extraClass;
+@endphp
+
+<grid cols="4" gap="lg" :class="$class" :id="$id">
+ @foreach ($items as $newsItem)
+ <news-item :item="$newsItem" />
+ @endforeach
+</grid>
--- /dev/null
+<article>
+
+ <a href="{{ $item->getURL($item->id) }}" class="news-index-article-image">
+ {!! $item->getThumbnailImage() !!}
+ </a>
+
+ <div class="news-index-article-text">
+ <time datetime="{{ $item->date->toDateTimeLocalString() }}"
+ class="block font-display font-medium text-navy mt-6 mb-2">
+ {{ $item->date->format('d/m/y') }}
+ </time>
+ <h4 class="text-2xl sm:text-xl">
+ <a class="text-navy" href="{{ $item->getURL($item->id) }}">
+ {{ $item->title }}
+ </a>
+ </h4>
+ <p>{{ $item['chapo'] }}</p>
+
+ <p><a href="{{ $item->getURL($item->id) }}">{{ __("Lire l'article") }}</a></p>
+ </div>
+
+</article>
</div>
@include('cubist::privacy.cookies-consent')
@include('cubist::body.end')
+@includeWhen(config('app.grid_overlay'), 'partials.grid-overlay')
</body>
</html>
<div class="mobile-bg"
style="background-image: url({{$page->getImageURLByCollection($slide['image'])}})"></div>
- <text-block :title="$slide['title']" title-tag="h1" title-class="h1 text-inherit">
+ <text-block :title="$slide['title']" title-tag="h1" title-class="h1 text-inherit mb-0.75v">
<p>
{!! nl2br($slide['text']) !!}
</p>
<text-block class="mb-2v" :title="__('Nos Produits')"/>
- <columns>
- <column>
+ {{-- ToDo: refactor these blocks + handle different image sizes and title lengths --}}
- <h3 class="h2 simple pl-1v md:text-2xl sm:pl-0">{{__('Capteurs')}}</h3>
-
- {{-- ToDo: refactor these blocks + handle different image sizes and title lengths --}}
-
- <grid cols="2" gap="md" class="xs:grid-cols-1 pr-1v border-r border-grey-300 sm:pr-0 sm:border-0">
- @foreach ($page->get('products_capteurs') as $highlight)
- @include('partials.home-products')
- @endforeach
- </grid>
-
- </column>
-
-
- <column>
- <h3 class="h2 simple pl-2v md:text-2xl sm:pl-0">{{__('Systèmes de mesure')}}</h3>
+ <grid cols="6" gap="md" class="lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2 xs:grid-cols-1">
+ @foreach ($page->get('products_capteurs') as $highlight)
+ @include('partials.home-products')
+ @endforeach
+ @foreach ($page->get('products_systems') as $highlight)
+ @include('partials.home-products')
+ @endforeach
+ </grid>
- <grid cols="2" gap="md" class="xs:grid-cols-1 pl-1v sm:pl-0">
- @foreach ($page->get('products_systems') as $highlight)
- @include('partials.home-products')
- @endforeach
- </grid>
- </column>
- </columns>
</content>
</full-width>
{{-- News --}}
<full-width>
<content>
- <text-block :title="__('Actualités')"/>
-
- <grid cols="4" class="md:grid-cols-2 xs:grid-cols-1 xs:grid-gap-20v">
- @foreach ($news as $newsItem)
- @php
- $newsURL = $nav->getHrefByID("news/{$newsItem->id}");
- @endphp
-
- <article>
- <a href="{{ $newsURL }}">
- {!! $newsItem->getThumbnailImage('mb-4') !!}
- </a>
- <h4 class="font-display">{{ $newsItem->title }}</h4>
- <p>{{ $newsItem['chapo'] }}</p>
-
- <p><a href="{{ $newsURL }}">{{ __('Lire la suite') }}</a></p>
- </article>
- @endforeach
- </grid>
+ <text-block :title="__('News')"/>
+ <news-grid :items="$news" />
</content>
</full-width>
@endif
<text-block :title="$news->title" title-tag="h1" />
<div class="news-post-layout">
- @if ($image)
- {{-- Todo: use larger image + srcset here --}}
- <img src="{{ $image }}" alt="{{ $alt }}" class="news-post-image">
- @endif
+ {!! $news->getThumbnailImage('news-post-image') !!}
<div class="news-post-body">
<time datetime="{{ $news->date->toDateTimeLocalString() }}" class="block mb-2 text-sm">
<a href="{{ $nav->getHrefByName('news') }}" class="font-display">
@svg('arrow-back', 'inline-block mr-2')
- {{ __('Retour aux actualités') }}
+ {{ __('Retour aux news') }}
</a>
</div>
@extends('layouts.app')
@section('content')
- @intro(['padding' => 'pb-2v'])
<full-width padding="pb-3v">
<content>
- {{-- Todo: use grid for main (generic?) sidebar layout and utilise minmax for content? --}}
- <div class="news-index-layout">
-
- <div class="news-index-articles">
- @foreach ($news as $newsItem)
- @php
- $newsURL = $nav->getHrefByID("news/{$newsItem->id}");
- @endphp
-
- <article class="news-index-article">
-
- <a href="{{ $newsURL }}" class="news-index-article-image">
- {!! $newsItem->getThumbnailImage() !!}
+ <text-block :title="$page->title" title-tag="h1" />
+
+ {{-- Featured News Item --}}
+ <columns class="items-center">
+ <column>
+ <a href="{{ $featured_news->getURL($featured_news->id) }}" class="news-index-article-image">
+ {!! $featured_news->getImage('featured') !!}
+ </a>
+ </column>
+
+ <column class="px-2v sm:px-0">
+ <time datetime="{{ $featured_news->date->toDateTimeLocalString() }}"
+ class="block font-display font-medium text-navy mb-4">
+ {{ $featured_news->date->format('d/m/y') }}
+ </time>
+ <h4 class="h1 simple leading-none mb-8">{{ $featured_news->title }}</h4>
+ <p class="font-display font-medium text-navy">{{ $featured_news->chapo }}</p>
+
+ <p>
+ <a class="btn" href="{{ $featured_news->getURL($featured_news->id) }}">
+ {{ __("Lire l'article") }}
</a>
-
- <div class="news-index-article-text">
- <time datetime="{{ $newsItem->date->toDateTimeLocalString() }}"
- class="block mb-2 text-sm">
- {{ $newsItem->date->format('d/m/y') }}
- </time>
- <h4 class="text-2xl">{{ $newsItem->title }}</h4>
- <p>{{ $newsItem['chapo'] }}</p>
-
- <p><a href="{{ $newsURL }}">{{ __('En savoir plus') }}</a></p>
- </div>
-
- </article>
- @endforeach
+ </p>
+ </column>
+ </columns>
+
+ {{-- News Grid --}}
+ <news-grid :items="$news" extra-class="mt-2v" id="newsGrid" />
+
+ @if ($more_news)
+ <div class="mt-1v flex justify-center">
+ <fetch-items label="{{ __("Voir plus d'articles") }}"
+ endpoint="/ajax/news"
+ data='@json(['exclude' => $featured_news->id])' {{-- Don't include featured item in query --}}
+ target="#newsGrid" {{-- Target CSS selector to append loaded items to --}}
+ class="bg-grey-200 hover:bg-grey-250 text-grey-dark font-display font-medium px-8 py-3 rounded-full">
+ </fetch-items>
</div>
-
- <div class="news-index-sidebar">
- <div class="bg-grey-100 p-1v mb-1v">
- <h3 class="text-2xl">{{ __('Évènements') }}</h3>
- @foreach($events as $event)
- @php
- $eventURL = $nav->getHrefByID("news/{$event->id}"); // Todo: possibly events will have different IDs
- @endphp
-
- <div class="event @if(!$loop->last) mb-8 @endif">
- <a href="{{ $eventURL }}">
- <img src="{{ $event->getFirstMediaUrl($event->image, 'details') }}"
- alt="{{ $event->getFirstMediaAlt($event->image, $event->title) }}"
- style="max-width:252px" class="mb-3">
- </a>
- <h4 class="font-medium mb-1">{{ $event->title }}</h4>
- <p>
- {{ $event->formatDateRange($event->event_start, $event->event_end) }},
- {{ $event->event_place }}
- </p>
- <p class="-mt-4"><a href="{{ $eventURL }}">{{ __('En savoir plus') }}</a></p>
- </div>
- @endforeach
- </div>
-
- <div class="bg-grey-100 p-1v">
- @include('partials.newsletter-form')
- </div>
- </div>
-
- </div>
+ @endif
</content>
</full-width>
<img class="contact-shortcut-icon" src="{{ asset('images/icon-phone.svg') }}" alt="{{ __('Téléphone') }}">
{{ $global->phone }}
</phone-link>
- <div class="contact-shortcut-tab">
+ <div class="contact-shortcut-tab mb-px">
<a href="/contact" class="p-3 bg-contactshortcut flex items-center pr-16">
<img class="contact-shortcut-icon" src="{{ asset('images/icon-email.svg') }}" alt="{{ __('Contact') }}">
{!! __('Formulaire de contact') !!}
</div>
--}}
</div>
+ <div class="contact-shortcut-tab">
+ <a href="https://pm-instrumentation.us20.list-manage.com/subscribe?id=a90b5c784a&u=ab875244e3b4c9c00072a0125"
+ target="_blank"
+ rel="noopener"
+ class="p-3 bg-contactshortcut flex items-center pr-16">
+ <img class="contact-shortcut-icon" src="{{ asset('images/icon-newsletter.svg') }}" alt="{{ __('Newsletter') }}">
+ {!! __("S'inscrire à la newsletter") !!}
+ </a>
+ </div>
</div>
--- /dev/null
+{{-- Layout Grid Overlay --}}
+<style>
+ #debugGrid {
+ background-size: 2.5vw 2.5vw;
+ background-image: linear-gradient(to right, grey 1px, transparent 1px), linear-gradient(to bottom, grey 1px, transparent 1px);
+ opacity: 0.3;
+ pointer-events: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ z-index: 9990;
+ }
+
+ @media (min-width: 1920px) {
+ #debugGrid {
+ background-size: 48px 48px;
+ max-width: 1921px;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+ }
+
+ /* Make sure scrollbars don't cause misalignment of grid */
+ /* Ref: https://dev.to/jonosellier/easy-overlay-scrollbars-variable-width-1mbh */
+ *::-webkit-scrollbar {
+ display: block;
+ width: 16px;
+ }
+
+ *::-webkit-scrollbar-button {
+ display: none;
+ }
+
+ *::-webkit-scrollbar-track {
+ background-color: #00000000;
+ }
+
+ *::-webkit-scrollbar-track-piece {
+ background-color: #00000000;
+ }
+
+ body {
+ overflow: overlay;
+ }
+
+ body {
+ --scrollbar-color: #00000000;
+ }
+
+ body.scrolling {
+ --scrollbar-color: #00000040 !important;
+ }
+
+ *::-webkit-scrollbar-thumb {
+ transition: background-color 0.5s;
+ background-color: #00000000;
+ border: 5px solid transparent;
+ border-radius: 24px;
+ box-shadow: 4px 0px 0px 4px var(--scrollbar-color) inset;
+ }
+
+ *::-webkit-scrollbar-thumb:hover {
+ background-color: var(--scrollbar-color);
+ border: 0px solid transparent;
+ box-shadow: none;
+ }
+
+</style>
+<script>
+ // Hide scrollbar when no longer actively scrolling
+ var scrollbarTimeout;
+
+ document.body.onscroll = () => {
+ if(scrollbarTimeout){
+ clearTimeout(scrollbarTimeout);
+ }
+ scrollbarTimeout = setTimeout(() => {
+ document.body.classList.remove('scrolling');
+ }, 1500);
+ document.body.classList.add('scrolling');
+ }
+</script>
+<div id="debugGrid"></div>
@endphp
@if($page)
-<div class="bg-white px-1v py-6 flex">
+<div class="bg-white px-6 py-6 flex">
<a class="animated-underline partial-underline flex flex-col justify-between w-full"
href="{{ $href }}">
<div class="flex items-center justify-center flex-grow">
class="bgimg"/>
</div>
</div>
- <p class="text-lg text-navy font-display font-medium">{{ $highlight['title'] }}</p>
+ <p class="text-lg md:text-base text-navy font-display font-medium">{{ $highlight['title'] }}</p>
</a>
</div>
@endif
// Sizes should be listed largest to smallest so they are generated in this order, allowing smaller
// breakpoints to take precedence over larger ones (eg. xs:p-1 should override sm:p-2)
//'xl': {'max': '1450px'}, // => @media (max-width: 1450px) { ... }
- //'lg': {'max': '1280px'}, // => @media (max-width: 1280px) { ... }
+ 'lg': {'max': '1280px'}, // => @media (max-width: 1280px) { ... }
'md': {'max': '1023px'}, // => @media (max-width: 1023px) { ... }
'sm': {'max': '767px'}, // => @media (max-width: 767px) { ... }
'xs': {'max': '499px'}, // => @media (max-width: 499px) { ... }