layout changes with media player and negative spacing at top of screen. updated sqlalchemy init for postgres deployment
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
<app-navbar></app-navbar>
|
||||
<app-media-player></app-media-player>
|
||||
<main>
|
||||
<main [class.no-stream]="!hasStream()">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
<app-footer></app-footer>
|
||||
|
||||
+29
-18
@@ -1,8 +1,6 @@
|
||||
@use '../styles/variables' as *;
|
||||
|
||||
// Bar heights (keep in sync so player sits flush below navbar)
|
||||
$navbar-height-mobile: 64px;
|
||||
$navbar-height-desktop: 52px;
|
||||
// Player height (matches media-player component min-height)
|
||||
$player-height: 48px;
|
||||
|
||||
app-navbar {
|
||||
@@ -11,26 +9,39 @@ app-navbar {
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 200;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
// App-level media player: shown on mobile (fixed strip), hidden on desktop (navbar instance shown)
|
||||
app-media-player {
|
||||
position: fixed;
|
||||
top: $navbar-height-mobile;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 199;
|
||||
}
|
||||
|
||||
main {
|
||||
padding-top: calc(#{$navbar-height-mobile} + #{$player-height});
|
||||
}
|
||||
|
||||
@media (min-width: #{$bp-md}) {
|
||||
app-media-player {
|
||||
top: $navbar-height-desktop;
|
||||
// Mobile: fixed strip below navbar
|
||||
@media (max-width: #{$bp-md - 1px}) {
|
||||
position: fixed;
|
||||
top: var(--navbar-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 199;
|
||||
}
|
||||
|
||||
// Desktop: hidden (navbar contains the visible instance)
|
||||
@media (min-width: $bp-md) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Main content padding
|
||||
@media (max-width: #{$bp-md - 1px}) {
|
||||
main {
|
||||
padding-top: calc(#{$navbar-height-desktop} + #{$player-height});
|
||||
padding-top: calc(var(--navbar-height) + #{$player-height});
|
||||
|
||||
&.no-stream {
|
||||
padding-top: var(--navbar-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $bp-md) {
|
||||
main {
|
||||
padding-top: var(--navbar-height);
|
||||
}
|
||||
}
|
||||
|
||||
+41
-5
@@ -1,4 +1,4 @@
|
||||
import { Component, effect, inject } from '@angular/core';
|
||||
import { Component, computed, effect, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { NavbarComponent } from './navbar/navbar.component';
|
||||
@@ -13,23 +13,59 @@ import { StationConfigService } from './services/station-config.service';
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
})
|
||||
export class App {
|
||||
export class App implements OnInit, OnDestroy {
|
||||
private titleService = inject(Title);
|
||||
private stationConfig = inject(StationConfigService);
|
||||
|
||||
readonly hasStream = computed(() => {
|
||||
const cfg = this.stationConfig.config();
|
||||
return !!cfg.stream_url && cfg.stream_url.trim().length > 0;
|
||||
});
|
||||
|
||||
private navbarEl: HTMLElement | null = null;
|
||||
private resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const config = this.stationConfig.config();
|
||||
const fullName = `${config.name_primary} ${config.name_secondary}`.trim();
|
||||
this.titleService.setTitle(fullName);
|
||||
|
||||
// Update favicon to use the station's logo_url.
|
||||
// Meta.updateTag() is unreliable for favicons in some browsers,
|
||||
// so we update the <link> element directly.
|
||||
const faviconEl = document.querySelector('link[rel="icon"]') as HTMLLinkElement | null;
|
||||
if (faviconEl && config.logo_url) {
|
||||
faviconEl.href = config.logo_url;
|
||||
}
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
if (this.hasStream()) {
|
||||
document.body.classList.add('has-stream');
|
||||
} else {
|
||||
document.body.classList.remove('has-stream');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Defer until the navbar is rendered in the DOM.
|
||||
queueMicrotask(() => {
|
||||
this.navbarEl = document.querySelector('app-navbar');
|
||||
if (!this.navbarEl) return;
|
||||
|
||||
this.updateNavbarHeight();
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => this.updateNavbarHeight());
|
||||
this.resizeObserver.observe(this.navbarEl!);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.resizeObserver?.disconnect();
|
||||
}
|
||||
|
||||
private updateNavbarHeight(): void {
|
||||
if (!this.navbarEl) return;
|
||||
const h = this.navbarEl.offsetHeight;
|
||||
document.documentElement.style.setProperty('--navbar-height', `${h}px`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-light);
|
||||
user-select: none;
|
||||
|
||||
// When inside navbar (desktop), remove the bottom border — navbar is one unit
|
||||
:global(body.has-stream) & {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.media-player__inner {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<app-media-player></app-media-player>
|
||||
|
||||
<button class="navbar-toggle" aria-label="Toggle menu" (click)="toggleMenu()">
|
||||
<span class="bar"></span>
|
||||
<span class="bar"></span>
|
||||
|
||||
@@ -4,14 +4,47 @@
|
||||
.navbar {
|
||||
background: var(--color-primary-dark);
|
||||
padding: $spacing-sm 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: $z-sticky;
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
:global(body.has-stream) & {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-inner {
|
||||
@include flex-between;
|
||||
|
||||
// Mobile: media player inside navbar is hidden (app.html instance shown as fixed strip)
|
||||
@media (max-width: #{$bp-md - 1px}) {
|
||||
app-media-player {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop: grid layout with media player inline
|
||||
@media (min-width: $bp-md) {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-areas: 'brand player menu';
|
||||
gap: $spacing-lg;
|
||||
|
||||
.navbar-brand {
|
||||
grid-area: brand;
|
||||
}
|
||||
|
||||
app-media-player {
|
||||
grid-area: player;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
grid-area: menu;
|
||||
}
|
||||
|
||||
.navbar-toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
@@ -181,7 +214,6 @@
|
||||
// Responsive
|
||||
@media (max-width: #{$bp-md - 1px}) {
|
||||
.navbar {
|
||||
height: 64px;
|
||||
padding: $spacing-xs 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CommonModule, UpperCasePipe } from '@angular/common';
|
||||
import { RouterLink, RouterLinkActive, Router } from '@angular/router';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { MediaPlayerComponent } from '../media-player/media-player.component';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { StationConfigService } from '../services/station-config.service';
|
||||
import { resolveImageUrl } from '../services/app-config.service';
|
||||
@@ -10,7 +11,7 @@ import { resolveImageUrl } from '../services/app-config.service';
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
standalone: true,
|
||||
imports: [CommonModule, UpperCasePipe, RouterLink, RouterLinkActive],
|
||||
imports: [CommonModule, UpperCasePipe, RouterLink, RouterLinkActive, MediaPlayerComponent],
|
||||
templateUrl: './navbar.component.html',
|
||||
styleUrl: './navbar.component.scss',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user