]> git.localhorst.tv Git - alttp.git/commitdiff
use event banners
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 10 Jul 2025 12:18:40 +0000 (14:18 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 10 Jul 2025 12:29:56 +0000 (14:29 +0200)
app/Http/Controllers/EventController.php
app/Models/Event.php
resources/js/components/episodes/Item.jsx
resources/js/components/events/Detail.jsx
resources/js/components/events/Item.jsx
resources/js/pages/Event.jsx
resources/js/pages/Front.jsx
resources/js/pages/Schedule.jsx
resources/sass/episodes.scss
resources/sass/events.scss
routes/web.php

index 94db8055a79cf1cddec8e7909a0537c8c41b5933..9d42b320e39a099c80a1f0d452d93cfe08cc9865 100644 (file)
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
 
 use App\Models\Event;
 use Carbon\Carbon;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
 use Illuminate\Http\Request;
 
 class EventController extends Controller
@@ -68,4 +69,18 @@ class EventController extends Controller
                return $event->toJson();
        }
 
+       public function web(Request $request, string $name) {
+               $event = Event::where('name', '=', $name)->first();
+               if ($event) {
+                       $view = view('app')
+                               ->with('title', $event->getTranslatedTitle())
+                               ->with('description', $event->getTranslatedShort());
+                       if ($event->banner) {
+                               $view = $view->with('image', url($event->banner));
+                       }
+                       return $view;
+               }
+               throw new ModelNotFoundException();
+       }
+
 }
index 331a384f8d1147deb04292552bdfec15a156eef4..8272f49e343b9b0a02188d6ce4d005d3213e098a 100644 (file)
@@ -18,6 +18,20 @@ class Event extends Model
                return $this->hasMany(Episode::class);
        }
 
+       public function getTranslatedTitle(): string {
+               if ($this->description) {
+                       return $this->description->getTranslatedProperty('title');
+               }
+               return $this->title;
+       }
+
+       public function getTranslatedShort(): string {
+               if ($this->description) {
+                       return $this->description->getTranslatedProperty('short');
+               }
+               return '';
+       }
+
        protected $casts = [
                'end' => 'datetime',
                'start' => 'datetime',
index 612c5ea85f6f76db13ca777f85a93fbebae70bf2..75f2c63c1c79cf0bf1be3d5b612c0bc43ff1d447 100644 (file)
@@ -48,14 +48,14 @@ const Item = ({ episode, onAddRestream, onApply, onEditRestream }) => {
                        </div>
                        <div className="episode-titlebar">
                                {episode.title || episode.event ?
-                                       <div className="episode-title fs-5 fs-md-4">
+                                       <h3 className="episode-title fs-5 fs-md-4">
                                                {episode.title || episode.event.title}
-                                       </div>
+                                       </h3>
                                : null}
                                {episode.comment ?
-                                       <div className="episode-comment">
+                                       <p className="episode-comment">
                                                {episode.comment}
-                                       </div>
+                                       </p>
                                : null}
                        </div>
                        <div className="episode-channel-links ms-auto text-end">
index 65895482d320dd6b1ca07b740794e0435cc2b2ce..d89bae1134540429d8b49f9971db9b48440a11fc 100644 (file)
@@ -30,6 +30,11 @@ const Detail = ({ actions, event }) => {
                                </Button>
                        : null}
                </div>
+               {event.banner ?
+                       <div className="event-banner-container">
+                               <div className="event-banner" style={{ backgroundImage: `url(${event.banner})` }} />
+                       </div>
+               : null}
                {event.description ?
                        <RawHTML html={getTranslation(event.description, 'description', i18n.language)} />
                : null}
@@ -46,6 +51,7 @@ Detail.propTypes = {
                editContent: PropTypes.func,
        }),
        event: PropTypes.shape({
+               banner: PropTypes.string,
                description: PropTypes.shape({
                }),
                end: PropTypes.string,
index 9217d56e4022b6b22c6c252a96a61bffc34f5314..0806454f4c2156de125f4ef72d1e211ed43082e8 100644 (file)
@@ -45,19 +45,27 @@ const Item = ({ event }) => {
                                        </> : null}
                                </div>
                        : null}
-                       {event.description?
-                               <div>
-                                       <RawHTML
-                                               html={getTranslation(event.description, 'description', i18n.language)}
-                                       />
-                               </div>
-                       : null}
+                       <div style={{ flexGrow: '1' }}>
+                               {event.banner ?
+                                       <Link to={getLink(event)}>
+                                               <div className="event-banner-container">
+                                                       <div className="event-banner" style={{ backgroundImage: `url(${event.banner})` }} />
+                                               </div>
+                                       </Link>
+                               : null}
+                               {event.description?
+                                               <RawHTML
+                                                       html={getTranslation(event.description, 'description', i18n.language)}
+                                               />
+                               : null}
+                       </div>
                </div>
        </li>;
 };
 
 Item.propTypes = {
        event: PropTypes.shape({
+               banner: PropTypes.string,
                corner: PropTypes.string,
                description: PropTypes.shape({
                }),
index ef52e9fb0a02ece0dcd539c176312640b22e9a47..ad2dd61bc7eff0a7a8dc0d1d9875ab9589d17cb3 100644 (file)
@@ -151,6 +151,10 @@ export const Component = () => {
                                content={getTranslation(event.description, 'short', i18n.language)}
                        />
                </Helmet> : null}
+               {event.banner ? <Helmet>
+                       <meta property="og:image" content={event.banner} />
+                       <meta property="twitter:image" content={event.banner} />
+               </Helmet> : null}
                <CanonicalLinks base={`/events/${event.name}`} />
                <Container>
                        <Detail actions={actions} event={event} />
index f0485ed1f078a12ee4de64abf8f30de2b083fca4..a83b6311d09cd862cb1e8d3f26a3484fe0d13475 100644 (file)
@@ -31,7 +31,7 @@ const Front = () => {
                                </Link>
                        </Col>
                        <Col sm={6}>
-                               <Link className="front-panel" to="/tournaments/6">
+                               <Link className="front-panel" to="/tournaments/7">
                                        <Image alt="" className="image" src="/media/alttp/front.png" />
                                        <div className="title">
                                                {t('front.circus')}
index 2b6a623a05006ef7ce4ec3e845447f04de1beb3d..98f321ecfa330d7f6e85c69b4451047ac20918d8 100644 (file)
@@ -13,7 +13,7 @@ import ApplyDialog from '../components/episodes/ApplyDialog';
 import Filter from '../components/episodes/Filter';
 import List from '../components/episodes/List';
 import RestreamDialog from '../components/episodes/RestreamDialog';
-import { invertEventFilter, toggleEventFilter } from '../helpers/Episode';
+import { invertEventFilter } from '../helpers/Episode';
 import { useUser } from '../hooks/user';
 
 export const Component = () => {
@@ -138,8 +138,8 @@ export const Component = () => {
                        setRestreamChannel(null);
                        setRestreamEpisode(null);
                        setShowRestreamDialog(false);
-               } catch (e) {
-                       toastr.error(t('episodes.restreamDialog.removeError'));
+               } catch (error) {
+                       toastr.error(t('episodes.restreamDialog.removeError', { error }));
                }
        }, []);
 
@@ -170,8 +170,8 @@ export const Component = () => {
                                ...newChannel,
                        }));
                        toastr.success(t('episodes.restreamDialog.editSuccess'));
-               } catch (e) {
-                       toastr.error(t('episodes.restreamDialog.editError'));
+               } catch (error) {
+                       toastr.error(t('episodes.restreamDialog.editError', { error }));
                }
        }, []);
 
@@ -196,8 +196,8 @@ export const Component = () => {
                                ...newChannel,
                        }));
                        toastr.success(t('episodes.restreamDialog.crewSuccess'));
-               } catch (e) {
-                       toastr.error(t('episodes.restreamDialog.crewError'));
+               } catch (error) {
+                       toastr.error(t('episodes.restreamDialog.crewError', { error }));
                }
        }, []);
 
index 02d2a70185b91f723612ef0f58010602e424ba2c..b76df22af37ef86a9f352ae2cdca13259669e812 100644 (file)
@@ -17,6 +17,8 @@
        background-size: 6rem auto;
        background-repeat: no-repeat;
        background-position: left bottom;
+       display: flex;
+       flex-direction: column;
        min-height: 8rem;
 
        &.is-active {
        .episode-start {
                width: 4rem;
        }
+       .episode-title {
+               font-weight: normal;
+               line-height: var(--bs-body-line-height);
+       }
+       .episode-comment {
+               margin-bottom: 0;
+       }
        @include media-breakpoint-up(md) {
                .episode-body {
                        margin-left: 5rem;
@@ -64,6 +73,7 @@
 
                img {
                        max-height: 3rem;
+                       height: 3rem;
                        width: auto;
                        border-radius: 50%;
                        margin: 0 0.5rem 0 0;
@@ -79,6 +89,7 @@
 
        img {
                max-height: 2rem;
+               height: 2rem;
                width: auto;
                border-radius: 50%;
                margin: 0 0.25rem 0 0;
index 918e9bf80fd4e0bf826e1248507d5ab72a03b7a9..5cbade29bc51934e51a43453c4237588648d1e49 100644 (file)
                width: 10rem;
        }
 }
+
+.event-banner-container {
+       position: relative;
+       margin-bottom: 1.5em;
+       padding-top: 25%;
+       @include media-breakpoint-up(md) {
+               padding-top: 20%;
+       }
+}
+.event-banner {
+       position: absolute;
+       top: 0;
+       left: 0;
+       width: 100%;
+       height: 100%;
+       background-position: center center;
+       background-size: cover;
+}
index 2dcf2980f5a5cde028d0eaeb78d3145a700e4aee..57a0d1ab4ee382896abd5891469deb95e49600e1 100644 (file)
@@ -20,23 +20,25 @@ use Jakyeru\Larascord\Http\Controllers\DiscordController;
 
 Route::get('/sitemap.xml', [SitemapXmlController::class, 'index']);
 
-Route::get('/dungeons/{name}', function($name) {
+Route::get('/events/{name}', 'App\Http\Controllers\EventController@web');
+
+Route::get('/dungeons/{name}', function ($name) {
        return app()->call('App\Http\Controllers\TechniqueController@web', ['type' => 'dungeon', 'name' => $name]);
 });
 
-Route::get('/locations/{name}', function($name) {
+Route::get('/locations/{name}', function ($name) {
        return app()->call('App\Http\Controllers\TechniqueController@web', ['type' => 'location', 'name' => $name]);
 });
 
-Route::get('/modes/{name}', function($name) {
+Route::get('/modes/{name}', function ($name) {
        return app()->call('App\Http\Controllers\TechniqueController@web', ['type' => 'mode', 'name' => $name]);
 });
 
-Route::get('/rulesets/{name}', function($name) {
+Route::get('/rulesets/{name}', function ($name) {
        return app()->call('App\Http\Controllers\TechniqueController@web', ['type' => 'ruleset', 'name' => $name]);
 });
 
-Route::get('/tech/{name}', function($name) {
+Route::get('/tech/{name}', function ($name) {
        return app()->call('App\Http\Controllers\TechniqueController@web', ['type' => 'tech', 'name' => $name]);
 });