feat(activities): brand-kit logo + app name in the events page header
Replace the bare "Events" h1 with the brand-kit logo paired with the
standalone's localized name. Deployers get per-standalone logo
control via branding/<dep>/icons/events/logo.{svg,png}; the
component itself stays brand-agnostic.
Brand-kit plumbing:
- `resolveAppLogo(app?)` in vite-branding.ts mirrors the resolution
chain pwa-assets.config.ts already uses for PWA icons
(per-standalone svg → png → global svg → png).
- `brandAppLogoAliasEntry(app)` returns a vite alias array entry. A
regex matches `@brand-app-logo` with or without a `?url` query so
the file resolves cleanly under either form.
- vite.events.config.ts switches its resolve.alias to the array form
so the per-standalone regex doesn't clash with the bare `@brand`
string alias.
Component side: a single `import brandAppLogoUrl from '@brand-app-logo?url'`
gives EventsPage the best-resolved logo without any fallback chain
in the component.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e3d77efec
commit
443c8b6a37
4 changed files with 86 additions and 9 deletions
|
|
@ -9,6 +9,7 @@ import {
|
|||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible'
|
||||
import { SlidersHorizontal, CalendarDays, Plus } from 'lucide-vue-next'
|
||||
import brandAppLogoUrl from '@brand-app-logo?url'
|
||||
import { useEvents } from '../composables/useEvents'
|
||||
import { useEventsStore } from '../stores/events'
|
||||
import EventSearchOverlay from '../components/EventSearchOverlay.vue'
|
||||
|
|
@ -75,8 +76,16 @@ function openCalendar() {
|
|||
|
||||
<template>
|
||||
<div class="container mx-auto py-4 px-4">
|
||||
<!-- Page header -->
|
||||
<h1 class="mb-3 text-xl sm:text-2xl font-bold text-foreground">
|
||||
<!-- Page header — brand-kit logo (per-standalone override or
|
||||
global) paired with the standalone's localized name. Resolved
|
||||
at build time via @brand-app-logo so deployers can override
|
||||
without touching this component. -->
|
||||
<h1 class="mb-3 flex items-center gap-2 text-xl sm:text-2xl font-bold text-foreground">
|
||||
<img
|
||||
:src="brandAppLogoUrl"
|
||||
:alt="t('events.title')"
|
||||
class="h-7 w-7 sm:h-8 sm:w-8 shrink-0"
|
||||
/>
|
||||
{{ t('events.title') }}
|
||||
</h1>
|
||||
|
||||
|
|
|
|||
9
src/vite-env.d.ts
vendored
9
src/vite-env.d.ts
vendored
|
|
@ -1,2 +1,11 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
|
||||
// Brand-kit alias for the active standalone's logo. Resolved at build
|
||||
// time by vite-branding.ts (per-standalone override or global). The
|
||||
// `?url` import returns the asset's URL string just like any other
|
||||
// vite static asset.
|
||||
declare module '@brand-app-logo?url' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { spawnSync } from 'node:child_process'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
import type { Plugin } from 'vite'
|
||||
|
||||
/**
|
||||
|
|
@ -43,6 +43,55 @@ export const brandAlias = {
|
|||
'@brand': BRAND_DIR,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Resolution order for the in-app logo of a given standalone. Mirrors
|
||||
* what pwa-assets.config.ts does for PWA icons: per-standalone override
|
||||
* first (SVG then PNG), then the brand's primary logo (SVG then PNG).
|
||||
*
|
||||
* Returned path is absolute so vite alias can map directly to it.
|
||||
*/
|
||||
export function resolveAppLogo(app?: string): string {
|
||||
const candidates: string[] = []
|
||||
if (app) {
|
||||
candidates.push(
|
||||
join(BRAND_DIR, 'icons', app, 'logo.svg'),
|
||||
join(BRAND_DIR, 'icons', app, 'logo.png'),
|
||||
)
|
||||
}
|
||||
candidates.push(
|
||||
join(BRAND_DIR, 'logo.svg'),
|
||||
join(BRAND_DIR, 'logo.png'),
|
||||
)
|
||||
const found = candidates.find((p) => existsSync(p))
|
||||
if (!found) {
|
||||
throw new Error(
|
||||
`No brand logo found for app="${app ?? ''}". Tried:\n ${candidates.join('\n ')}\n` +
|
||||
`See branding/README.md for the brand kit contract.`,
|
||||
)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
/**
|
||||
* Standalone-aware brand-logo alias entry. Append to a vite config's
|
||||
* `resolve.alias` array alongside the rest of the alias map. The
|
||||
* regex matches `@brand-app-logo` with or without a `?url` query so
|
||||
* `import logoUrl from '@brand-app-logo?url'` resolves to the active
|
||||
* standalone's logo file (per-app override or global), with no
|
||||
* fallback chain in the component itself.
|
||||
*
|
||||
* Note: when used with the object form of resolve.alias, a bare
|
||||
* `@brand` entry would shadow this — combine the two as an array
|
||||
* (see vite.events.config.ts).
|
||||
*/
|
||||
export function brandAppLogoAliasEntry(app?: string) {
|
||||
const resolved = resolveAppLogo(app)
|
||||
return {
|
||||
find: /^@brand-app-logo(\?.*)?$/,
|
||||
replacement: `${resolved}$1`,
|
||||
} as const
|
||||
}
|
||||
|
||||
/**
|
||||
* PWA manifest name for a standalone. Combines the brand name with the
|
||||
* app's own label, or returns the bare brand when no label is given.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@ import { defineConfig, type Plugin } from 'vite'
|
|||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
import { brand, brandAlias, brandAssetsPlugin, brandManifestName } from './vite-branding'
|
||||
import {
|
||||
brand,
|
||||
brandAlias,
|
||||
brandAppLogoAliasEntry,
|
||||
brandAssetsPlugin,
|
||||
brandManifestName,
|
||||
} from './vite-branding'
|
||||
|
||||
/**
|
||||
* Plugin to rewrite dev server requests to events.html
|
||||
|
|
@ -117,10 +123,14 @@ export default defineConfig(({ mode }) => ({
|
|||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
...brandAlias,
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
// Array form so we can mix the per-standalone logo regex (needs to
|
||||
// match `@brand-app-logo?url` query suffix) with the bare string
|
||||
// aliases without one shadowing the other.
|
||||
alias: [
|
||||
brandAppLogoAliasEntry('events'),
|
||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||
],
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist-events',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue