Front End Audio Player Always Available
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
<MudStack Row AlignItems="AlignItems.Center" Spacing="3" Class="mx-3">
|
||||
@if (Icon != null)
|
||||
{
|
||||
<MudIcon Icon="@Icon" />
|
||||
}
|
||||
<NavLink href="@Href" Match="@(Match ?? NavLinkMatch.Prefix)">
|
||||
@if (ChildContent != null)
|
||||
<NavLink href="@Href" Match="@(Match ?? NavLinkMatch.Prefix)" class="nav-menu-item">
|
||||
<div class="nav-item-content">
|
||||
@if (Icon != null)
|
||||
{
|
||||
@ChildContent
|
||||
<MudIcon Icon="@Icon" class="nav-item-icon" />
|
||||
}
|
||||
</NavLink>
|
||||
</MudStack>
|
||||
<span class="nav-item-text">
|
||||
@if (ChildContent != null)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</NavLink>
|
||||
@@ -1 +1,71 @@
|
||||
|
||||
/* Navigation menu item styling */
|
||||
.nav-menu-item {
|
||||
display: block;
|
||||
padding: 8px 16px;
|
||||
margin: 4px 8px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.nav-item-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-item-text {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Hover state */
|
||||
.nav-menu-item:hover {
|
||||
background-color: rgba(138, 43, 226, 0.08);
|
||||
color: #8A2BE2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-menu-item:hover .nav-item-content {
|
||||
color: var(--mud-palette-tertiary);
|
||||
}
|
||||
|
||||
.nav-menu-item:hover .nav-item-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Active state */
|
||||
.nav-menu-item.active {
|
||||
background-color: rgba(138, 43, 226, 0.12);
|
||||
color: var(--mud-palette-primary);
|
||||
border-left: 4px solid #8A2BE2;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.nav-menu-item.active .nav-item-content {
|
||||
color: var(--mud-palette-primary);
|
||||
}
|
||||
|
||||
.nav-menu-item.active .nav-item-icon {
|
||||
color: var(--mud-palette-primary);
|
||||
}
|
||||
|
||||
.nav-menu-item.active .nav-item-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Focus state */
|
||||
.nav-menu-item:focus {
|
||||
outline: 2px solid #8A2BE2;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@@ -1,55 +1,87 @@
|
||||
<MudPaper MaxWidth="1600px" Square="true">
|
||||
<MudStack Row AlignItems="AlignItems.Center" Spacing="4" Class="px-4 py-2">
|
||||
<MudStack Class="pb-2">
|
||||
<MudStack Row AlignItems="AlignItems.Center">
|
||||
<MudIconButton Icon="@GetPlayIcon()"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
OnClick="@TogglePlayPause"
|
||||
Disabled="!IsLoaded"/>
|
||||
@if (IsLoaded)
|
||||
{
|
||||
<MudIconButton Icon="Icons.Material.Filled.Stop"
|
||||
Color="Color.Primary"
|
||||
OnClick="@Stop"
|
||||
Disabled="!IsLoaded"/>
|
||||
}
|
||||
</MudStack>
|
||||
<MudStack Row AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.body2" Class="font-monospace deepdrft-audio-time">
|
||||
@FormatTime(CurrentTime) / @(Duration.HasValue ? FormatTime(Duration.Value) : "--:--")
|
||||
</MudText>
|
||||
@if (!IsLoaded)
|
||||
{
|
||||
<MudProgressCircular Color="Color.Tertiary" Value="@LoadProgress" Size="Size.Small"/>
|
||||
}
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
@if (_isMinimized)
|
||||
{
|
||||
<div class="deepdrft-minimized-player-dock">
|
||||
<MudIconButton Icon="@GetPlayIcon()"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
Class="deepdrft-minimized-player-button"
|
||||
OnClick="@ToggleMinimized" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Full-width outer container *@
|
||||
<div class="deepdrft-player-outer-container">
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="deepdrft-player-inner-container">
|
||||
<div class="deepdrft-audio-player-bar">
|
||||
<div class="deepdrft-audio-player-content">
|
||||
|
||||
@* Controls section *@
|
||||
<div class="deepdrft-audio-controls-section">
|
||||
<div class="deepdrft-audio-buttons-row">
|
||||
<MudIconButton Icon="@GetPlayIcon()"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
OnClick="@TogglePlayPause"
|
||||
Disabled="!IsLoaded"/>
|
||||
@if (IsLoaded)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Stop"
|
||||
Color="Color.Primary"
|
||||
OnClick="@Stop"/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="deepdrft-audio-info-row">
|
||||
<MudText Typo="Typo.body2" Class="font-monospace deepdrft-audio-time">
|
||||
@FormatTime(CurrentTime) / @(Duration.HasValue ? FormatTime(Duration.Value) : "--:--")
|
||||
</MudText>
|
||||
@if (!IsLoaded)
|
||||
{
|
||||
<MudProgressCircular Color="Color.Tertiary" Value="@LoadProgress" Size="Size.Small"/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MudSlider T="double"
|
||||
Min="0"
|
||||
Max="@(Duration ?? 0D)"
|
||||
Step="0.1"
|
||||
Value="@CurrentTime"
|
||||
ValueChanged="@OnSeek"
|
||||
Disabled="!IsLoaded"
|
||||
Class="deepdrft-audio-slider-seek"/>
|
||||
@* Seek slider *@
|
||||
<MudSlider T="double"
|
||||
Min="0"
|
||||
Max="@(Duration ?? 0D)"
|
||||
Step="0.1"
|
||||
Value="@CurrentTime"
|
||||
ValueChanged="@OnSeek"
|
||||
Disabled="!IsLoaded"
|
||||
Class="deepdrft-audio-seek-slider"/>
|
||||
|
||||
<div class="deepdrft-audio-controls">
|
||||
<MudIcon Icon="@GetVolumeIcon()" Class="deepdrft-audio-volume-icon"/>
|
||||
<MudSlider T="double"
|
||||
Min="0"
|
||||
Max="1"
|
||||
Step="0.01"
|
||||
Value="@Volume"
|
||||
ValueChanged="@OnVolumeChange"
|
||||
Class="deepdrft-audio-slider"/>
|
||||
</div>
|
||||
</MudStack>
|
||||
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" ShowCloseIcon="true" CloseIconClicked="ClearError">
|
||||
@ErrorMessage
|
||||
</MudAlert>
|
||||
}
|
||||
</MudPaper>
|
||||
@* Volume section *@
|
||||
<div class="deepdrft-audio-volume-section">
|
||||
<MudIcon Icon="@GetVolumeIcon()" Class="deepdrft-audio-volume-icon"/>
|
||||
<MudSlider T="double"
|
||||
Min="0"
|
||||
Max="1"
|
||||
Step="0.01"
|
||||
Value="@Volume"
|
||||
ValueChanged="@OnVolumeChange"
|
||||
Class="deepdrft-audio-volume-slider"/>
|
||||
</div>
|
||||
<div class="deepdrft-audio-minimize-section">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Minimize"
|
||||
Color="Color.Secondary"
|
||||
Size="Size.Small"
|
||||
OnClick="@ToggleMinimized"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" ShowCloseIcon="true" CloseIconClicked="ClearError" Class="ma-2">
|
||||
@ErrorMessage
|
||||
</MudAlert>
|
||||
}
|
||||
</div>
|
||||
</MudContainer>
|
||||
</div>
|
||||
|
||||
@* Spacer div to maintain layout spacing *@
|
||||
<div class="deepdrft-player-spacer"></div>
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public partial class AudioPlayerBar : ComponentBase
|
||||
{
|
||||
[CascadingParameter] public required IPlayerService PlayerService { get; set; }
|
||||
[Parameter] public bool ShowLoadProgress { get; set; } = true;
|
||||
private bool _isMinimized = true;
|
||||
|
||||
private bool IsLoaded => PlayerService.IsLoaded;
|
||||
private bool IsPlaying => PlayerService.IsPlaying;
|
||||
@@ -70,4 +71,10 @@ public partial class AudioPlayerBar : ComponentBase
|
||||
PlayerService.ClearError();
|
||||
}
|
||||
|
||||
private void ToggleMinimized()
|
||||
{
|
||||
_isMinimized = !_isMinimized;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/* AudioPlayerBar Component - Scoped Styles */
|
||||
|
||||
/* Outer container - full width, fixed to bottom */
|
||||
.deepdrft-player-outer-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
z-index: 1200;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Inner container wrapper */
|
||||
.deepdrft-player-inner-container {
|
||||
padding: 1rem;
|
||||
padding-bottom: 1.5rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Player bar with rounded corners and semi-opaque background */
|
||||
.deepdrft-audio-player-bar {
|
||||
position: relative;
|
||||
background: rgba(var(--mud-palette-surface-rgb), 0.75);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-radius: 1rem;
|
||||
border: 1px solid rgba(var(--mud-palette-divider-rgb), 0.9);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s ease;
|
||||
color: var(--mud-palette-text-primary);
|
||||
overflow: hidden;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Spacer div to maintain layout spacing */
|
||||
.deepdrft-player-spacer {
|
||||
height: 140px;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Minimized floating dock */
|
||||
.deepdrft-minimized-player-dock {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 60px;
|
||||
z-index: 1300;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(var(--mud-palette-primary-rgb), 0.95) 0%,
|
||||
rgba(var(--mud-palette-secondary-rgb), 0.9) 50%,
|
||||
rgba(var(--mud-palette-tertiary-rgb), 0.95) 100%
|
||||
);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 2px solid rgba(var(--mud-palette-secondary-rgb), 0.7);
|
||||
box-shadow: 0 4px 20px rgba(var(--mud-palette-primary-rgb), 0.6),
|
||||
0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.deepdrft-minimized-player-dock:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 25px rgba(var(--mud-palette-primary-rgb), 0.8),
|
||||
0 3px 15px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.deepdrft-minimized-player-button {
|
||||
border-radius: 50%;
|
||||
background: transparent !important;
|
||||
color: white;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
width: 48px !important;
|
||||
height: 48px !important;
|
||||
}
|
||||
|
||||
/* Layout containers */
|
||||
.deepdrft-audio-player-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
}
|
||||
|
||||
.deepdrft-audio-controls-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.deepdrft-audio-buttons-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.deepdrft-audio-info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.deepdrft-audio-volume-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.deepdrft-audio-minimize-section {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Control elements */
|
||||
.deepdrft-audio-seek-slider {
|
||||
flex: 1;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.deepdrft-audio-volume-slider {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.deepdrft-audio-time {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.deepdrft-audio-volume-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.deepdrft-minimized-player-dock {
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.deepdrft-minimized-player-button {
|
||||
width: 44px !important;
|
||||
height: 44px !important;
|
||||
}
|
||||
|
||||
.deepdrft-player-inner-container {
|
||||
padding: 0.75rem;
|
||||
padding-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.deepdrft-audio-player-bar {
|
||||
border-radius: 1rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.deepdrft-audio-player-content {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.deepdrft-audio-controls-section {
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.deepdrft-audio-seek-slider {
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.deepdrft-audio-volume-section {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.deepdrft-player-spacer {
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
@using DeepDrftWeb.Client.Services
|
||||
|
||||
<CascadingValue Value="@(PlayerService)" IsFixed="true">
|
||||
<CascadingValue Value="@(PlayerService)" IsFixed="true">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
@@ -13,14 +13,6 @@ public partial class AudioPlayerService : ComponentBase
|
||||
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
// PlayerService is already created as a field, so it's immediately available to cascading components
|
||||
// It will be in uninitialized state until OnAfterRenderAsync when AudioPlaybackEngine is ready
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<MudLayout>
|
||||
<AudioPlayerService>
|
||||
@* <MudThemeManagerButton OnClick="@((e) => OpenThemeManager(true))" /> *@
|
||||
<MudThemeManager Open="_themeManagerOpen" OpenChanged="OpenThemeManager" Theme="_themeManager" ThemeChanged="UpdateTheme" />
|
||||
@* <MudThemeManager Open="_themeManagerOpen" OpenChanged="OpenThemeManager" Theme="_themeManager" ThemeChanged="UpdateTheme" /> *@
|
||||
<MudAppBar Elevation="_themeManager.AppBarElevation">
|
||||
<MudAvatar Class="mr-2">
|
||||
<MudImage Src="img/deepdrft-logo.jpg"></MudImage>
|
||||
@@ -16,10 +16,12 @@
|
||||
<NavMenu />
|
||||
<MudSpacer/>
|
||||
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End"/>
|
||||
@* <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End"/> *@
|
||||
</MudAppBar>
|
||||
<MudMainContent Class="pt-16 pa-4" Style="min-height: 100vh">
|
||||
@Body
|
||||
<MudMainContent Class="pt-16 deepdrft-layout-with-overlay-player">
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
|
||||
@Body
|
||||
</MudContainer>
|
||||
<AudioPlayerBar />
|
||||
</MudMainContent>
|
||||
</AudioPlayerService>
|
||||
|
||||
@@ -33,12 +33,6 @@
|
||||
<Folder Include="wwwroot\js\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<TypeScriptCompile Include="Interop\webaudio.ts">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</TypeScriptCompile>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Prevent TypeScript compilation issues during publish -->
|
||||
<PropertyGroup>
|
||||
<TypeScriptCompileOnSaveEnabled>false</TypeScriptCompileOnSaveEnabled>
|
||||
|
||||
@@ -72,12 +72,12 @@ class AudioPlayer {
|
||||
try {
|
||||
this.bufferChunks.push(audioBlock);
|
||||
this.currentSize += audioBlock.length;
|
||||
|
||||
|
||||
if (this.expectedSize > 0 && this.onLoadProgressCallback) {
|
||||
const progress = (this.currentSize / this.expectedSize) * 100;
|
||||
this.onLoadProgressCallback(Math.min(progress, 100));
|
||||
}
|
||||
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: (error as Error).message };
|
||||
@@ -89,24 +89,24 @@ class AudioPlayer {
|
||||
const arrayBuffer = new ArrayBuffer(this.currentSize);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
let offset = 0;
|
||||
|
||||
|
||||
for (const chunk of this.bufferChunks) {
|
||||
view.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
|
||||
this.audioBuffer = await this.audioContext!.decodeAudioData(arrayBuffer);
|
||||
this.duration = this.audioBuffer.duration;
|
||||
|
||||
|
||||
this.bufferChunks = [];
|
||||
this.currentSize = 0;
|
||||
|
||||
|
||||
if (this.onLoadProgressCallback) {
|
||||
this.onLoadProgressCallback(100);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
return {
|
||||
success: true,
|
||||
duration: this.duration,
|
||||
sampleRate: this.audioBuffer.sampleRate,
|
||||
numberOfChannels: this.audioBuffer.numberOfChannels,
|
||||
@@ -202,18 +202,18 @@ class AudioPlayer {
|
||||
|
||||
try {
|
||||
const wasPlaying = this.isPlaying;
|
||||
|
||||
|
||||
if (this.isPlaying) {
|
||||
this.source!.stop();
|
||||
}
|
||||
|
||||
this.pauseOffset = position;
|
||||
|
||||
|
||||
if (wasPlaying) {
|
||||
this.source = this.audioContext!.createBufferSource();
|
||||
this.source.buffer = this.audioBuffer;
|
||||
this.source.connect(this.gainNode!);
|
||||
|
||||
|
||||
this.source.onended = () => {
|
||||
this.isPlaying = false;
|
||||
this.isPaused = false;
|
||||
@@ -424,11 +424,11 @@ const DeepDrftAudio = {
|
||||
if (!player) {
|
||||
return { success: false, error: "Player not found" };
|
||||
}
|
||||
|
||||
|
||||
player.setOnProgressCallback((currentTime: number) => {
|
||||
dotNetObjectReference.invokeMethodAsync(methodName, currentTime);
|
||||
});
|
||||
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
@@ -437,11 +437,11 @@ const DeepDrftAudio = {
|
||||
if (!player) {
|
||||
return { success: false, error: "Player not found" };
|
||||
}
|
||||
|
||||
|
||||
player.setOnEndCallback(() => {
|
||||
dotNetObjectReference.invokeMethodAsync(methodName);
|
||||
});
|
||||
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
@@ -450,11 +450,11 @@ const DeepDrftAudio = {
|
||||
if (!player) {
|
||||
return { success: false, error: "Player not found" };
|
||||
}
|
||||
|
||||
|
||||
player.setOnLoadProgressCallback((progress: number) => {
|
||||
dotNetObjectReference.invokeMethodAsync(methodName, progress);
|
||||
});
|
||||
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
|
||||
@@ -293,29 +293,28 @@ body, p, span, div,
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Audio Player Layout */
|
||||
.deepdrft-audio-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.deepdrft-audio-time {
|
||||
min-width: 120px;
|
||||
}
|
||||
/* Layout with overlay audio player - Global layout class */
|
||||
/*.deepdrft-layout-with-overlay-player {*/
|
||||
/* position: relative;*/
|
||||
/* min-height: calc(100vh - 64px);*/
|
||||
/* padding-bottom: 160px; !* Increased space for overlay player *!*/
|
||||
/*}*/
|
||||
|
||||
.deepdrft-audio-volume-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
/*!* Audio player overlay positioning - Global positioning *!*/
|
||||
/*.deepdrft-layout-with-overlay-player > .AudioPlayerBar,*/
|
||||
/*.deepdrft-layout-with-overlay-player > *:last-child {*/
|
||||
/* position: fixed;*/
|
||||
/* bottom: 0;*/
|
||||
/* left: 0;*/
|
||||
/* right: 0;*/
|
||||
/* z-index: 1000;*/
|
||||
/* pointer-events: none;*/
|
||||
/*}*/
|
||||
|
||||
.deepdrft-audio-slider {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.deepdrft-audio-slider-seek {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
}
|
||||
/*.deepdrft-layout-with-overlay-player > *:last-child > * {*/
|
||||
/* pointer-events: auto;*/
|
||||
/*}*/
|
||||
|
||||
/* Responsive Utilities */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -4,6 +4,7 @@ ssh-add /c/.ssh/deepdrft_ed25519
|
||||
|
||||
CONTENT_PROJ="DeepDrftContent"
|
||||
WEB_PROJ="DeepDrftWeb"
|
||||
WEB_SERVICES_PROJ="DeepDrftWeb.Services"
|
||||
CONTENT_APP="deepdrft-content.tar.gz"
|
||||
WEB_APP="deepdrft-web.tar.gz"
|
||||
|
||||
@@ -16,15 +17,15 @@ WEB_MIG="deepdrft-migrations.sql"
|
||||
REMOTE="deepdrft@dch5.snailbird.net"
|
||||
WEB_APPROOT="/deepdrft/web"
|
||||
|
||||
LATEST_MIGRATION=$(dotnet ef migrations list --project $WEB_PROJ --context DeepDrftContext --no-build | tail -1)
|
||||
LATEST_MIGRATION=$(dotnet ef migrations list --project $WEB_SERVICES_PROJ --context DeepDrftContext --no-build | tail -1)
|
||||
REMOTE_MIGRATION=$(ssh $REMOTE "sqlite3 $WEB_APPROOT/Database/deepdrft.db 'SELECT MigrationId FROM __EFMigrationsHistory ORDER BY MigrationId DESC LIMIT 1;'" 2>/dev/null || echo "")
|
||||
|
||||
if [ "$LATEST_MIGRATION" != "$REMOTE_MIGRATION" ]; then
|
||||
echo "Generating migration script from $REMOTE_MIGRATION to $LATEST_MIGRATION..."
|
||||
if [ -z "$REMOTE_MIGRATION" ]; then
|
||||
dotnet ef migrations script --project $WEB_PROJ --context DeepDrftContext --output $WEB_MIG --verbose --no-build
|
||||
dotnet ef migrations script --project $WEB_SERVICES_PROJ --context DeepDrftContext --output $WEB_MIG --verbose --no-build
|
||||
else
|
||||
dotnet ef migrations script $REMOTE_MIGRATION --project $WEB_PROJ --context DeepDrftContext --output $WEB_MIG --verbose --no-build
|
||||
dotnet ef migrations script $REMOTE_MIGRATION --project $WEB_SERVICES_PROJ --context DeepDrftContext --output $WEB_MIG --verbose --no-build
|
||||
fi
|
||||
APPLY_MIGRATIONS=true
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user