Adds website theme serialization system
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
<button class="tab-btn" [class.active]="activeTab() === 'underwriters'"
|
||||
(click)="activeTab.set('underwriters')">Underwriters</button>
|
||||
<button class="tab-btn" [class.active]="activeTab() === 'mobile-builds'"
|
||||
(click)="activeTab.set('mobile-builds')">Mobile Builds</button>
|
||||
(click)="activeTab.set('mobile-builds'); loadThemeJsonStatus()">Mobile Builds</button>
|
||||
<button class="tab-btn" [class.active]="activeTab() === 'stats'" (click)="activeTab.set('stats')">Stats</button>
|
||||
</div>
|
||||
|
||||
@@ -437,6 +437,33 @@
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="build-card">
|
||||
<h3>Theme Export</h3>
|
||||
@if (themeJsonStatus$ | async; as status) {
|
||||
@if (status.exists) {
|
||||
<div class="theme-status-ok">
|
||||
<div><strong>theme.json</strong> — ready</div>
|
||||
<div>Path: {{ status.path }}</div>
|
||||
<div>Generated: {{ status.generated_at }}</div>
|
||||
<div>Size: {{ status.size }} bytes</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="theme-status-missing">
|
||||
<p><strong>theme.json</strong> not found at {{ status.path }}</p>
|
||||
<p>Regenerate to create it from the current station config.</p>
|
||||
</div>
|
||||
}
|
||||
<button class="btn btn-outline" (click)="regenerateThemeJson()"
|
||||
[disabled]="themeJsonLoading$ | async">
|
||||
{{ (themeJsonLoading$ | async) ? 'Regenerating...' : 'Regenerate' }}
|
||||
</button>
|
||||
} @else {
|
||||
@if (themeJsonLoading$ | async) {
|
||||
<p class="empty-row">Loading...</p>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="build-card">
|
||||
<h3>Create Build Request</h3>
|
||||
|
||||
|
||||
@@ -539,6 +539,60 @@
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
// ── Theme export status ──────────────────────────────────
|
||||
|
||||
.theme-status-ok,
|
||||
.theme-status-missing {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
border-radius: $radius-sm;
|
||||
margin-bottom: $spacing-sm;
|
||||
font-size: 0.88rem;
|
||||
color: $neutral-dark;
|
||||
}
|
||||
|
||||
.theme-status-ok {
|
||||
background: rgba($success-green, 0.08);
|
||||
border: 1px solid rgba($success-green, 0.25);
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.theme-status-missing p {
|
||||
margin: 0 0 $spacing-xs;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-status-missing {
|
||||
background: rgba($accent-orange, 0.08);
|
||||
border: 1px solid rgba($accent-orange, 0.25);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 2px solid $primary-blue;
|
||||
color: $primary-blue;
|
||||
border-radius: $radius-md;
|
||||
padding: $spacing-xs $spacing-md;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background $transition-fast, color $transition-fast;
|
||||
margin-top: $spacing-sm;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: $primary-blue;
|
||||
color: $neutral-white;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@include responsive(md) {
|
||||
.mobile-build-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Underwriter } from '../interfaces/underwriter';
|
||||
import {
|
||||
MobileBuildDefaults,
|
||||
MobileBuildRequest,
|
||||
ThemeStatus,
|
||||
} from '../interfaces/mobile-build';
|
||||
import { ShowService } from '../services/show.service';
|
||||
import { EventService } from '../services/event.service';
|
||||
@@ -126,6 +127,10 @@ export class AdminComponent implements OnInit {
|
||||
mobileBuildPreflightIssues: string[] = [];
|
||||
mobileBuildBusy = false;
|
||||
|
||||
// ── Theme JSON status (mobile build tab) ────────────────────
|
||||
readonly themeJsonStatus$ = new BehaviorSubject<ThemeStatus | null>(null);
|
||||
readonly themeJsonLoading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
buildAndroid = true;
|
||||
buildIos = true;
|
||||
androidApplicationId = '';
|
||||
@@ -412,6 +417,36 @@ export class AdminComponent implements OnInit {
|
||||
this.onMobileBuildSelected(this.selectedMobileBuildId);
|
||||
}
|
||||
|
||||
// ── Theme JSON status ──────────────────────────────────────
|
||||
|
||||
async loadThemeJsonStatus(): Promise<void> {
|
||||
try {
|
||||
this.themeJsonLoading$.next(true);
|
||||
this.themeJsonStatus$.next(await firstValueFrom(
|
||||
this.mobileBuildService.getThemeStatus(),
|
||||
));
|
||||
} catch {
|
||||
this.themeJsonStatus$.next(null);
|
||||
} finally {
|
||||
this.themeJsonLoading$.next(false);
|
||||
}
|
||||
}
|
||||
|
||||
async regenerateThemeJson(): Promise<void> {
|
||||
try {
|
||||
this.themeJsonLoading$.next(true);
|
||||
await firstValueFrom(this.mobileBuildService.regenerateTheme());
|
||||
await this.loadThemeJsonStatus();
|
||||
} catch (err) {
|
||||
this.mobileBuildErrorMessage = this.getErrorMessage(
|
||||
err,
|
||||
'Failed to regenerate theme.json.',
|
||||
);
|
||||
} finally {
|
||||
this.themeJsonLoading$.next(false);
|
||||
}
|
||||
}
|
||||
|
||||
getArtifactDownloadHref(path: string): string {
|
||||
return `${getAppConfig().apiBaseUrl}${path}`;
|
||||
}
|
||||
|
||||
@@ -67,3 +67,10 @@ export interface MobileBuildPreflightResult {
|
||||
passed: boolean;
|
||||
issues: string[];
|
||||
}
|
||||
|
||||
export interface ThemeStatus {
|
||||
exists: boolean;
|
||||
path: string;
|
||||
generated_at: string | null;
|
||||
size: number | null;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MobileBuildPreflightResult,
|
||||
MobileBuildRequest,
|
||||
MobileBuildUploadedFile,
|
||||
ThemeStatus,
|
||||
} from '../interfaces/mobile-build';
|
||||
|
||||
@Injectable({
|
||||
@@ -66,4 +67,17 @@ export class MobileBuildService {
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
getThemeStatus(): Observable<ThemeStatus> {
|
||||
return this.http.get<ThemeStatus>(
|
||||
`${getAppConfig().apiBaseUrl}/api/theme/status`,
|
||||
);
|
||||
}
|
||||
|
||||
regenerateTheme(): Observable<unknown> {
|
||||
return this.http.post(
|
||||
`${getAppConfig().apiBaseUrl}/api/theme/regenerate`,
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user