use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
-class Kernel extends ConsoleKernel
-{
+class Kernel extends ConsoleKernel {
/**
* The Artisan commands provided by your application.
*
/**
* Define the application's command schedule.
*
- * @param \Illuminate\Console\Scheduling\Schedule $schedule
+ * @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
- protected function schedule(Schedule $schedule)
- {
+ protected function schedule(Schedule $schedule) {
$schedule->command('job:dispatchNow PitchShiftAudio')->hourly();
+ $schedule->command('job:dispatchNow SyncNotion')->everyTenMinutes();
}
/**
*
* @return void
*/
- protected function commands()
- {
- $this->load(__DIR__.'/Commands');
+ protected function commands() {
+ $this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
use Cubist\Backpack\Http\Controllers\CubistPWAController;
use Cubist\Util\Files\Files;
use Illuminate\Support\Facades\Session;
+use Notion\Blocks\BlockInterface;
+use Notion\Blocks\Paragraph;
+use Notion\Common\RichText;
+use Notion\Notion;
+use Notion\Pages\Properties\RichTextProperty;
+use Notion\Pages\Properties\Title;
-class FrontController extends Controller
-{
- public static function defaultCollection()
- {
+class FrontController extends Controller {
+ public static function defaultCollection() {
return redirect('/' . Collection::withoutGlobalScope('ownerclause')->find(1)->slug);
}
- public function collection($name)
- {
+ public function collection($name) {
$collection = Collection::withoutGlobalScope('ownerclause')->where('slug', $name)->first();
if (null === $collection) {
return self::defaultCollection();
if ($p = $this->checkPassword($collection)) {
return $p;
}
+
+ if ($collection->notion_key && $collection->notion_database) {
+ return $this->notionCollection($collection);
+ }
+
$lists = CollectionList::withoutGlobalScope('ownerclause')->where('collection', $collection->id)->get();
$songs = $this->_getSongsOfCollection($collection->id, $lists);
return view('collection', ['menu' => true, 'songs' => $songs, 'collection' => $collection, 'collection_songs' => $songs, 'collection_lists' => $lists]);
}
- public function checkPassword(Collection $collection)
- {
+ /**
+ * @throws \Exception
+ */
+ public function notionCollection($collection) {
+ $songs = self::getSongsOfNotionCollection($collection);
+ return view('notion_collection', ['menu' => true, 'notion' => true, 'songs' => $songs, 'collection' => $collection, 'collection_songs' => $songs]);
+ }
+
+
+ public function checkPassword(Collection $collection) {
if (!$collection->password) {
return false;
}
return view('login', ['menu' => false, 'collection' => $collection, 'error' => $error]);
}
- public function song($collection, $song)
- {
+ public function song($collection, $song) {
/** @var Collection $collection */
$collection = Collection::withoutGlobalScope('ownerclause')->where('slug', $collection)->first();
if (null === $collection) {
abort(404);
}
+ if ($collection->notion_key && $collection->notion_database) {
+ return $this->notionSong($collection, $song);
+ }
/** @var Song $song */
$song = Song::withoutGlobalScope('ownerclause')->where('slug', $song)->first();
if (null === $song) {
if ($p = $this->checkPassword($collection)) {
return $p;
}
+
+
$lists = CollectionList::withoutGlobalScope('ownerclause')->where('collection', $collection->id)->get();
$partition = false;
$lyrics_html = '';
return view('song', ['menu' => true, 'lyrics_html' => $lyrics_html, 'song' => $song, 'collection' => $collection, 'partition' => $partition, 'collection_songs' => $this->_getSongsOfCollection($collection->id, $lists), 'collection_lists' => $lists]);
}
- protected function _getSongsOfCollection($id, $lists)
- {
+ protected function notionSong($collection, $song) {
+ $partition = false;
+
+ $u = $song . '.html';
+ $songs = self::getSongsOfNotionCollection($collection);
+ foreach ($songs as $song) {
+ if ($song->getUrl() === $u) {
+ break;
+ }
+ }
+ $lyrics_html = $song->getLyrics();
+ return view('notion_song', ['menu' => true, 'notion' => true, 'lyrics_html' => $lyrics_html, 'song' => $song, 'collection' => $collection, 'collection_songs' => $songs]);
+ }
+
+ protected function _getSongsOfCollection($id, $lists) {
/** @var Song $q */
$q = Song::withoutGlobalScope('ownerclause')->whereRaw('json_contains(collections, \'["' . $id . '"]\')');
foreach ($lists as $list) {
}
- public function manifest($collection)
- {
+ /**
+ * @param $collection Collection
+ * @return \App\Notion\Song[]
+ * @throws \Exception
+ */
+ public static function getSongsOfNotionCollection($collection, $force = false) {
+ $cacheKey = 'songs_notion_' . $collection->notion_database . '_' . $collection->id;
+ $ttl = 900;
+ if ($force) {
+ $v = self::_getSongsOfNotionCollection($collection);
+ cache()->set($cacheKey, $v, $ttl);
+ } else {
+ return cache()->remember($cacheKey, $ttl, function () use ($collection) {
+ return self::_getSongsOfNotionCollection($collection);
+ });
+ }
+ }
+
+
+ protected static function _getSongsOfNotionCollection($collection) {
+
+ $audios = ['mp3', 'm4a', 'wav'];
+ $notion = Notion::create($collection->notion_key);
+ $database = $notion->databases()->find($collection->notion_database);
+ $songs = $notion->databases()->queryAllPages($database);
+
+ $res = [];
+ foreach ($songs as $song) {
+ $pageContents = '';
+ try {
+ $contents = $notion->blocks()->findChildren($song->id);
+ foreach ($contents as $content) {
+ $pageContents .= self::_blockToHtml($content);
+ }
+ } catch (\Exception $e) {
+
+ }
+
+ $s = [];
+
+ /** @var Title $titleProperty */
+ $titleProperty = $song->getProprety('Titre');
+ /** @var Number $orderProperty */
+ $orderProperty = $song->getProprety('Ordre');
+ /** @var RichTextProperty $lyricsProperty */
+ $lyricsProperty = $song->getProprety('Paroles');
+ /** @var \Notion\Pages\Properties\Files $filesProperty */
+ $filesProperty = $song->getProprety('Files & media');
+
+ $s['id'] = $song->id;
+ $s['order'] = (int)$orderProperty->number;
+ $s['title'] = $titleProperty->toString();
+ $s['lyrics'] = self::_blockToHtml($lyricsProperty);
+ $s['notes'] = $pageContents;
+ $s['notion_url'] = $song->url;
+
+
+ $s['files'] = [];
+ $s['audios'] = [];
+ foreach ($filesProperty->files as $file) {
+ $ext = self::parseExtensionFromAmazonURL($file->url);
+ if (in_array($ext, $audios)) {
+ $s['audios'][] = ['name' => self::parseFileNameFromAmazonURL($file->url), 'url' => $file->url, 'ext' => $ext];
+ } else {
+ $s['files'][] = ['name' => self::parseFileNameFromAmazonURL($file->url), 'url' => $file->url, 'ext' => $ext];
+ }
+ }
+
+ $res[$s['order']] = new \App\Notion\Song($s, $collection->notion_database);
+ }
+ ksort($res);
+ return $res;
+
+ }
+
+ /**
+ * @param $block RichTextProperty|BlockInterface
+ * @return string
+ */
+ protected static function _blockToHtml($block) {
+ $start = '';
+ $end = '';
+ if ($block instanceof Paragraph) {
+ $start = '<p>';
+ $end = '</p>';
+ }
+ $res = '';
+ foreach ($block->text as $rt) {
+ $res .= self::_richTextToHtml($rt);
+ }
+ $res = $start . $res . $end;
+ return str_replace('</h3><br />', '</h3>', $res);
+ }
+
+ /**
+ * @param $rt RichText
+ * @return string
+ */
+ protected static function _richTextToHtml($rt) {
+ $start = '';
+ $end = '';
+ if ($rt->annotations->isBold) {
+ $start = '<strong>' . $start;
+ $end = $end . '</strong>';
+ }
+ if ($rt->annotations->isItalic) {
+ $start = '<em>' . $start;
+ $end = $end . '</em>';
+ }
+ if ($rt->annotations->isUnderline) {
+ $start = '<h3>' . $start;
+ $end = $end . '</h3>';
+ }
+ if ($rt->annotations->isCode) {
+ $start = '<blockquote>' . $start;
+ $end = $end . '</blockquote>';
+ }
+ return $start . nl2br($rt->plainText) . $end;
+ }
+
+ public static function parseFileNameFromAmazonURL($url) {
+ $u = parse_url($url);
+ $p = explode('/', $u['path']);
+ $f = array_pop($p);
+ $e = explode('.', $f);
+ array_pop($e);
+ $f = implode('.', $e);
+ return str_replace('_', ' ', $f);
+ }
+
+ public static function parseExtensionFromAmazonURL($url) {
+ $u = parse_url($url);
+ $p = explode('/', $u['path']);
+ $f = array_pop($p);
+ $e = explode('.', $f);
+ return array_pop($e);
+ }
+
+ public function manifest($collection) {
$collection = Collection::withoutGlobalScope('ownerclause')->where('slug', $collection)->first();
if (null === $collection) {
abort(404);
$icons[count($icons) - 1]['purpose'] = 'maskable';
$res = ['name' => $collection->name,
- 'short_name' => $collection->shortname ?: $collection->name,
- 'description' => '',
- 'display' => 'standalone',
- 'orientation' => 'any',
- 'background_color' => $collection->splashscreen_color,
- 'theme_color' => $collection->theme_color,
- 'start_url' => '/' . $collection->slug,
- 'scope' => '/' . $collection->slug,
- 'icons' => $icons];
+ 'short_name' => $collection->shortname ?: $collection->name,
+ 'description' => '',
+ 'display' => 'standalone',
+ 'orientation' => 'any',
+ 'background_color' => $collection->splashscreen_color,
+ 'theme_color' => $collection->theme_color,
+ 'start_url' => '/' . $collection->slug,
+ 'scope' => '/' . $collection->slug,
+ 'icons' => $icons];
return response(json_encode($res))->header('Content-Type', 'application/manifest+json');
}
- public function downloadAssets($songId)
- {
+ public function downloadAssets($songId) {
/** @var Song $song */
$song = Song::withoutGlobalScope('ownerclause')->find($songId);
$fields = ['partition', 'lyrics_doc'];
namespace App\Jobs;
use App\Models\Song;
+use Cubist\Backpack\Jobs\Base;
use Cubist\Util\Files\Files;
use Cubist\Util\PHP;
-use Illuminate\Bus\Queueable;
-use Illuminate\Contracts\Queue\ShouldQueue;
-use Illuminate\Foundation\Bus\Dispatchable;
-use Illuminate\Queue\InteractsWithQueue;
-use Illuminate\Queue\SerializesModels;
use YoutubeDl\Options;
use YoutubeDl\YoutubeDl;
-class DownloadAudioTracks implements ShouldQueue
-{
- use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+class DownloadAudioTracks extends Base{
public function handle()
{
use App\Models\Song;
use Cubist\Backpack\Jobs\Base;
use Cubist\Util\CommandLine;
-use Cubist\Util\Files\Files;
use Cubist\Util\PHP;
-use YoutubeDl\Options;
-use YoutubeDl\YoutubeDl;
class OptimizeMP3 extends Base
{
namespace App\Jobs;
use App\Models\Song;
+use Cubist\Backpack\Jobs\Base;
use Cubist\Util\CommandLine;
use Cubist\Util\PHP;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
-class PitchShiftAudio implements ShouldQueue
-{
- use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+class PitchShiftAudio extends Base {
public static $pitches = [1 => '+1', 2 => '+2', 3 => '+3', 4 => '+4', 5 => '+5', 6 => '+6', 7 => '-5', 8 => '-4', 9 => '-3', 10 => '-2', 11 => '-1'];
- public function handle()
- {
+ public function handle() {
PHP::neverStop();
DownloadAudioTracks::dispatchSync();
--- /dev/null
+<?php
+
+namespace App\Jobs;
+
+use App\Http\Controllers\FrontController;
+use App\Models\Collection;
+use Cubist\Backpack\Jobs\Base;
+use Cubist\Util\PHP;
+use Notion\Common\RichText;
+use Notion\Notion;
+use Notion\Pages\Properties\Files;
+use Notion\Pages\Properties\Number;
+use Notion\Pages\Properties\RichTextProperty;
+use Notion\Pages\Properties\Title;
+
+class SyncNotion extends Base {
+
+ /**
+ * @throws \Exception
+ */
+ public function handle() {
+ PHP::neverStop();
+
+ foreach (Collection::withoutGlobalScopes()->get() as $collection) {
+ if ($collection->notion_key && $collection->notion_database) {
+ FrontController::getSongsOfNotionCollection($collection, true);
+ }
+ }
+ }
+}
use Cubist\Backpack\Magic\Fields\Slug;
use Cubist\Backpack\Magic\Fields\Table;
use Cubist\Backpack\Magic\Fields\Text;
+use Cubist\Backpack\Magic\Fields\URL;
use Cubist\Backpack\Magic\Models\CubistMagicAbstractModel;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
$this->addField('agenda', FilesOrURL::class, 'Planning / Agenda', ['default' => false, 'database_default' => false]);
$this->addField('organisation_name', Text::class, 'Label du lien "Rรฉpartition / organisation"', ['default' => false, 'database_default' => false]);
$this->addField('organisation', FilesOrURL::class, 'Rรฉpartition / organisation', ['default' => false, 'database_default' => false]);
+ $this->addField('notion_key',Text::class,'Clรฉ Notion');
+ $this->addField('notion_database',Text::class,'Base de donnรฉes Notion');
+ $this->addField('notion_home',URL::class,'Home Notion');
}
protected function _getFreeFileBaseDirectory() {
--- /dev/null
+<?php
+
+namespace App\Notion;
+
+use Cubist\Util\Data;
+use Cubist\Util\Str;
+
+class Song extends Data{
+
+ public function getUrl() {
+ return Str::slug($this->getTitle()) . '.html';
+ }
+
+ public function getArtist() {
+ return '';
+ }
+
+ public function getTitle() {
+ return $this->get('title');
+ }
+
+ public function getLyrics() {
+ return $this->get('lyrics');
+ }
+
+ public function getId() {
+ return $this->get('id');
+ }
+
+ public function getOrder() {
+ return $this->get('order');
+ }
+
+ public function getNotionURL(){
+ return $this->get('notion_url');
+ }
+
+ public function hasSimpleChords(){
+ return false;
+ }
+
+ public function getAudioTracks() {
+ $res = $this->get('audios');
+ foreach ($res as $k=>$v) {
+ $res[$k]['i']=$k;
+ $res[$k]['opturl']=$v['url'];
+ }
+ return $res;
+ }
+
+ public function getFiles(){
+ return $this->get('files');
+ }
+
+}
],
"license": "MIT",
"require": {
- "php": ">=7.4",
+ "php": ">=8.1",
+ "ext-dom": "*",
"ext-json": "*",
+ "ext-libxml": "*",
+ "ext-redis": "*",
"ext-simplexml": "*",
"ext-tidy": "*",
"ext-zip": "*",
- "ext-libxml": "*",
- "ext-dom": "*",
- "ext-redis": "*",
"cubist/cms-back": "dev-master",
"fruitcake/laravel-cors": "^2.2",
+ "guzzlehttp/psr7": "^2.0",
"league/csv": "^9.8",
+ "mariosimao/notion-sdk-php": "^1.0",
"mxl/laravel-job": "^1.3",
"norkunas/youtube-dl-php": "dev-master",
"php-ffmpeg/php-ffmpeg": "^0.18.0",
- "phpoffice/phpspreadsheet": "^1.23"
+ "phpoffice/phpspreadsheet": "^1.25"
+ },
+ "replace": {
+ "cviebrock/laravel-elasticsearch": "*"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.10",
font-weight: 700
src: url('fonts/roboto-condensed-700.woff2') format('woff2')
+@font-face
+ font-family: 'Dancing Script'
+ font-style: normal
+ font-weight: 400
+ src: url('fonts/dancing-script-v24-latin-regular.woff2') format('woff2')
+
$slab: 'Roboto Slab', sans-serif
.phpdebugbar
overflow: hidden
text-overflow: ellipsis
+
span
color: #000
@media (prefers-color-scheme: dark)
color: #fff
font-size: 0.65em
+ &.order
+ font-size: 1em
+ margin-right: 10px
li
list-style: none
@media (prefers-color-scheme: dark)
color: #fff !important
+ &.fromnotion
+ margin: 0 !important
+ font-family: $slab !important
+ border-bottom: 1px solid currentColor
+ padding-bottom: 20px
+
+ &.notes
+ font-family: "Dancing Script", cursive !important
+ padding-top: 20px
+ font-size: 1.25em
+
&.lyrics
.lyrics
top: -0.25em
opacity: 0.5
+ h2
+ font-size: 20px
+ margin: 20px 0 15px
+
+ ul
+ li
+ list-style: none
+
h3
color: var(--theme-color)
margin: 5px 0
+ a.select_audio
+ cursor: pointer
+
+ blockquote
+ border-left: 5px solid var(--theme-color)
+ padding-left: 20px
+ opacity: 0.6
+
+
.lyrics
font-family: "Roboto Slab", serif
font-weight: 400
$('body').addClass('init');
});
+setTimeout(function () {
+ $('body').addClass('init');
+}, 2000);
+$(function () {
+ $('body').addClass('init');
+});
+
$(window).on('beforeunload', function () {
$('body').removeClass('init');
});
$('input[name="' + n + '"]').prop('checked', value == '1');
}
-function updateSelect(select) {
+window.updateSelect = function (select) {
var sv = $(select).val();
if (sv === null) {
return;
}
- console.log('Set option from select', $(select).data('name'), sv);
$(select).closest('.clickselect').find('span').text($(select).find('option[value="' + $(select).val() + '"]').text());
setOption($(select).data('name'), $(select).val());
updateSongView();
return true;
});
+
$(document).on('change', '.checkbox-switch', function () {
var name = $(this).attr('name');
var checked = $(this).is(':checked') ? '1' : '0';
resetPlayers();
});
+ $(document).on('click', '.select_audio', function () {
+ var e = $(this).attr('data-id').split('_');
+ $('select[data-name="audio"]').val(e[1]);
+ updateSelect($('select[data-name="audio"]'));
+ resetPlayers();
+ playAudio();
+ return false;
+ });
+
+
function resetPlayers() {
$.each(window.players, function (k, player) {
player.stop();
});
});
$(document).on('click', '[data-action="play"]', function () {
- $(this).hide();
+ playAudio();
+ return false;
+ });
+
+ function playAudio() {
+ $('[data-action="play"]').hide();
var audio = window.getOption('audio').toString();
var t = window.getOption('audio_' + audio + '_tone');
var p = window.players[pid];
$(p.elements.container).show();
p.play();
- return false;
- });
+ }
function changeAudio() {
$('li[data-audio]').hide();
+@if(isset($notion) && $notion)
+@include('notion_menu')
+@else
@include('menu')
- <!DOCTYPE html>
+@endif
+<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title')</title>
<style>
- body {
- --theme-color: {{$collection->theme_color}};
- --plyr-color-main: {{$collection->theme_color}};
- }
+ body {
+ --theme-color: {{$collection->theme_color}};
+ --plyr-color-main: {{$collection->theme_color}};
+ }
</style>
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
<link rel="manifest" href="/{{$collection->slug}}.webmanifest">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>var THEME_COLOR = '{{$collection->theme_color}}';
@if(isset($song))
- var SONG = {{$song->id}};
+ var SONG = "{{$song->id}}";
@endif
</script>
</head>
--- /dev/null
+@extends('layout')
+@php
+ $preload=[];
+@endphp
+@section('title', $collection->name)
+
+@section('content')
+ @include('header',['title'=>$collection->name,'subtitle'=>''])
+ <article class="collection">
+ <nav>
+ <ul>
+ @foreach($songs as $csong)
+ @include('notion_collection_song')
+ @endforeach
+ </ul>
+ </nav>
+ </article>
+@endsection
--- /dev/null
+@php
+ $songurl='/'.$collection->slug.'/'.$csong->getURL();
+@endphp
+<li><a href="{{$songurl}}"><span class="order">{{$csong->getOrder()}}</span> {{$csong->getTitle()}}<span>{{$csong->getArtist()}}</span></a>
+</li>
--- /dev/null
+@php
+ $tones=\App\Field\Tone::getTones();
+ $fontSizes=[0.5,0.6,0.7,0.8,0.9,1,1.1,1.25,1.5,1.75,2];
+ $prefetch=[];
+@endphp
+
+@section('menu')
+ <nav id="menu">
+ <ul id="panel-menu">
+ <li><a href="/{{$collection->slug}}">๐ {{__('Home')}}</a></li>
+ <li><span>๐ถ {{__('Songs')}}</span>
+ <ul>
+ @if(isset($collection_songs))
+ @foreach($collection_songs as $csong)
+ @include('notion_menu_song')
+ @endforeach
+ @endif
+ </ul>
+ </li>
+ @if(isset($song))
+{{-- @if($song->hasChords())--}}
+{{-- <li><a href="#" class="noaction">๐ค {{__('Show lyrics')}} <input type="checkbox"--}}
+{{-- class="checkbox-switch"--}}
+{{-- name="show_lyrics_{{$song->id}}"--}}
+{{-- data-default="1"></a></li>--}}
+{{-- <li><a href="#" class="noaction">๐ผ {{__('Show chords')}} <input type="checkbox"--}}
+{{-- class="checkbox-switch"--}}
+{{-- name="show_chords_{{$song->id}}"--}}
+{{-- data-default="1"></a></li>--}}
+{{-- @if($song->hasSimpleChords())--}}
+{{-- <li><a href="#" class="noaction">๐ฃ {{__('Show simple chords')}} <input type="checkbox"--}}
+{{-- class="checkbox-switch"--}}
+{{-- name="show_simplechords_{{$song->id}}"--}}
+{{-- data-default="0"></a>--}}
+{{-- </li>--}}
+{{-- @endif--}}
+{{-- @if($collection->transpose)--}}
+{{-- <li><a href="#" class="clickselect">โ๏ธ {{__('Key')}} <select data-name="tone"--}}
+{{-- name="tone_{{$song->id}}">--}}
+{{-- @for($i=-5;$i<=6;$i++)--}}
+{{-- <option value="{{$i}}"--}}
+{{-- @if($i===0) selected @endif>{{$tones[(12+$song->key+$i)%12].$song->mode}} @if($i>0)--}}
+{{-- (+{{$i}}--}}
+{{-- )--}}
+{{-- @elseif($i<0)--}}
+{{-- ({{$i}})--}}
+{{-- @endif </option>--}}
+{{-- @endfor--}}
+{{-- </select><span></span></a></li>--}}
+{{-- @endif--}}
+{{-- @else--}}
+{{-- <li class="hidden"><a href="#" class="noaction">{{__('Show chords')}} <input type="checkbox"--}}
+{{-- class="checkbox-switch"--}}
+{{-- name="show_chords_{{$song->id}}"--}}
+{{-- data-default="0"></a>--}}
+{{-- </li>--}}
+{{-- <li class="hidden"><a href="#" class="noaction">{{__('Show lyrics')}} <input type="checkbox"--}}
+{{-- class="checkbox-switch"--}}
+{{-- name="show_lyrics_{{$song->id}}"--}}
+{{-- data-default="1"></a>--}}
+{{-- </li>--}}
+{{-- @endif--}}
+
+ <li><a href="#" class="clickselect">๐ {{__('Lyrics text size')}} <select data-name="size"
+ name="size_{{$song->id}}">
+ @foreach($fontSizes as $s)
+ <option value="{{$s}}" @if($s===1) selected @endif>{!! round($s*100) !!}%</option>
+ @endforeach
+ </select><span></span></a></li>
+
+ @if(count($song->getAudioTracks())>0)
+ <li><a href="#" class="clickselect">๐ฟ {{__('Audio track')}} <select data-name="audio"
+ name="audio_{{$song->id}}">
+ @foreach($song->getAudioTracks() as $i=>$track)
+ <option value="{{$i}}" data-url="{{$track['url']}}">{{$track['name']}}</option>
+ @endforeach
+ </select><span></span></a></li>
+ @foreach($song->getAudioTracks() as $i=>$track)
+ @if($collection->transpose && isset($track['tone']) && is_numeric($track['tone']))
+ <li data-audio="{{$i}}"><a href="#" class="clickselect">โ๏ธ{{__('Audio key')}} <select
+ class="audiotone"
+ data-name="audio_{{$i}}_tone"
+ name="audio_{{$i}}_tone_{{$song->id}}">
+ @for($i=-5;$i<=6;$i++)
+ <option value="{{($i+12)%12}}"
+ @if($i===0) selected @endif>{{$tones[(12+$track['tone']+$i)%12].$song->mode}} @if($i>0)
+ (+{{$i}}
+ )
+ @elseif($i<0)
+ ({{$i}})
+ @endif </option>
+ @endfor
+ </select><span></span></a></li>
+ @endif
+ @endforeach
+ @endif
+{{-- @if($partition)--}}
+{{-- <li><a target="_blank" href="{{$partition}}">๐ผ {{__('Partition')}}</a></li>--}}
+{{-- @endif--}}
+{{-- @if($collection->download_assets)--}}
+{{-- <li><a download="{{$song->title}}.zip" href="/downloadassets/{{$song->id}}">โฌ๏ธ {{__('Tรฉlรฉcharger')}}</a></li>--}}
+{{-- @endif--}}
+ @endif
+
+ @if(!isset($song) && $collection->agenda)
+ @php
+ $planning=\Cubist\Util\Url::isLocal($collection->agenda)?'/collection/'.$collection->id.'/'.$collection->agenda:$collection->agenda;
+ @endphp
+ <li>
+ <span>๐๏ธ {{__('Planning')}}</span>
+ <ul>
+ <iframe src="{{$planning}}" style="width: 100%;height:100%;border: 0;"></iframe>
+ </ul>
+ </li>
+ @endif
+
+ @if($collection->organisation_name)
+ <li>
+ <a target="_blank" href="{{$collection->organisation}}">๐งโ๐ค๏ธ {{$collection->organisation_name}}</a>
+ </li>
+ @endif
+
+ <li>
+ <span><img
+ src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data={{rawurlencode('https://songbook.enhydra.fr/'.$collection->slug)}}"
+ style="width: 15px;height:auto;margin:5px 10px 5px 5px;vertical-align: bottom">{{__('Share via QR Code')}}</span>
+ <ul>
+ <div style="padding: 30px;background-color: #fff;height: 100%"><img
+ src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data={{rawurlencode('https://songbook.enhydra.fr/'.$collection->slug)}}"
+ style="width: 100%;height:auto;"></div>
+ </ul>
+ </li>
+ @if(isset($song))
+ <li><a href="{{$song->getNotionURL()}}">๐ง {{__('La chanson sur Notion')}}</a></li>
+ @else
+ <li><a href="{{$collection->notion_home}}">๐ง {{__('Les chansons sur Notion')}}</a></li>
+ @endif
+ </ul>
+ </nav>
+@endsection
+
+@section('prefetch')
+ <script>var PRELOAD =@json($prefetch);</script>
+@endsection
--- /dev/null
+@php
+ $songurl="/$collection->slug/".$csong->getUrl();
+ $prefetch[]=$songurl;
+@endphp
+<li><a rel="prefetch" href="{{$songurl}}">{{trim($csong->getOrder().' - '.$csong->getTitle().' - '.$csong->getArtist(),' -')}}</a>
+</li>
--- /dev/null
+@php
+ $audioTracks=$song->getAudioTracks();
+ $simple=$song->hasSimpleChords();
+@endphp
+
+@extends('layout')
+@section('title', $song->title.' - '.$song->artist.' - '. $collection->name)
+@section('content')
+ @include('header',['title'=>$song->title,'subtitle'=>$song->artist])
+ <article class="song" data-tone="{{$song->key}}" data-mode="{{$song->mode}}">
+ <section class="fromnotion">{!! $lyrics_html !!}</section>
+ @if($song->notes)
+ <section class="fromnotion notes">{!! $song->notes !!}</section>
+ @endif
+ <div class="credits">{{$song->credits}}</div>
+ @if(count($audioTracks))
+ <h2>๐ฟ {{__('Audios')}}</h2>
+ <ul class="files">
+ @foreach($audioTracks as $audio)
+ <li><a class="select_audio" data-id="player_{{$audio['i']}}_0">{{$audio['name']}}</a></li>
+ @endforeach
+ </ul>
+ @endif
+ @if(count($song->getFiles()))
+ <h2>๐ {{__('Fichiers')}}</h2>
+ <ul class="files">
+ @foreach($song->getFiles() as $file)
+ <li><a target="_blank" href="{{$file['url']}}">{{$file['name']}}.{{$file['ext']}}</a></li>
+ @endforeach
+ </ul>
+ @endif
+ </article>
+ @if(count($audioTracks))
+ <div id="audioplayers">
+ @foreach($audioTracks as $audio)
+ <audio id="player_{{$audio['i']}}_0" controls loop>
+ <source src="{{$audio['opturl']}}" type="audio/mp3"/>
+ </audio>
+ @endforeach
+ </div>
+ @endif
+@endsection
+@section('floating')
+ @if(count($audioTracks))
+ <a href="#" data-action="play">
+ <svg x="0" y="0" width="124.512" height="124.512" aria-hidden="true">
+ <use xlink:href="#play"/>
+ </svg>
+ </a>
+ @endif
+@endsection