refactor: remove outdated documentation and update gallery showcase with last upload feature

- Deleted obsolete documentation files related to color system, discover modal architecture, and store-actions pattern.
- Updated the GalleryShowcase component to replace 'createdAt' with 'lastUpload' for better clarity on photo upload timing.
- Enhanced localization files to include a new key for 'lastUpload' in both English and Chinese.
- Modified the FeaturedGalleriesService to fetch and include the last updated timestamp for galleries.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2025-12-06 22:20:03 +08:00
parent c9514607b7
commit 7d70361717
7 changed files with 30 additions and 216 deletions

View File

@@ -1,92 +0,0 @@
# Pastel Colors
> A color library and sample site for design systems, offering three styles: Regular, Kawaii, and High Contrast. It covers application-level colors (accent/primary/secondary), semantic colors (text, background, border, separator, link, disabled), material transparency layers (ultraThick→opaque), and grayscale gradients (gray1→gray10). All colors are defined using OKLCH and offer light/dark variants to maintain consistent perceptual contrast and accessibility across themes and light/dark modes.
This project includes a color package (`packages/colors`) and a demo/documentation site (`docs`). Colors are created using the `createColor` tool to ensure consistency and composability. Styles:
- Regular: Universal, neutral default style suitable for most products
- Kawaii: Softer, cute-style light-color contrast controls
- High Contrast: Extreme contrast for enhanced readability and accessibility (WCAG-friendly)
Color Organization Structure and Recommendations:
- Application Colors: `accent`, `primary`, `secondary` (for branding, primary actions, secondary buttons, etc.)
- Semantic Colors: `text`, `placeholderText`, `border`, `separator`, `link`, `disabledControl`, `disabledText`; plus `background` and `fill` with progressive hierarchy (primary→quinary / primary→quaternary)
- Material Colors: `ultraThick`, `thick`, `medium`, `thin`, `ultraThin`, `opaque` (same color with varying opacity levels, used for frosted glass, card overlays, etc.)
- GrayScale: `gray1``gray10` (progresses and inverts under light/dark modes, supporting neutral interfaces and layering)
- Prioritize conveying intent through Application and Semantic categories before mapping specific hues; maintain semantic naming across light/dark modes, with variants automatically adjusting contrast.
- Theme Overview:
- Regular: Default universal contrast with modern neutral palette, emphasizing balanced text/background gradation and soft colors.
- Kawaii: Soft pastel hues (pink, blue, purple, cyan, etc.) with low saturation; refined text/borders suited for cute/warm-toned interfaces.
- High Contrast: Enhanced text/background contrast, improved link/interaction visibility; used for accessibility-first or noisy environments.
- Application (Purpose):
- accent: Brand accents, emphasis elements (logos, highlighted button borders/hover states, selected indicators)
- primary: Primary actions/visuals (main buttons, key links, progress/highlighting)
- secondary: Secondary actions/less prominent emphasis (secondary buttons, informational labels, auxiliary chart colors)
- Semantic (Purpose):
- text
- primary: Body/heading main text
- secondary: Secondary descriptions, de-emphasized supplementary information
- tertiary: Placeholder or minor-level labels/meta information
- quaternary: Disabled state or extremely low-priority text
- placeholderText
- primary: Input field placeholder text
- border
- primary: Primary border for cards/inputs/popups
- secondary: Weak grouping/block separator border
- separator
- primary: List item/module separator
- link
- primary: Text links, clickable text
- disabledControl
- primary: Background/border for disabled controls
- disabledText
- primary: Disabled text color
- background (primary→quinary): Progressive background gradient for pages/cards/overlays/inner containers
- fill (primary→quaternary): Progressive fill gradient for icons/controls
- material (ultraThick→opaque): Opacity levels for frosted glass/overlay/card materials
- Gray Scale (Purpose):
- gray1→gray10: Light-to-dark and dark-to-light grayscale gradients for background layering, strokes, and text softening; recommended with `separator` and `border` for stable hierarchy.
- Hue Usage (Cross-Theme Recommendations):
- blue / indigo: Primary actions, links, interactive elements—emphasizing reliability and technological feel
- cyan / sky / teal: Information prompts, secondary highlights—emphasizing clarity and freshness
- green / emerald / lime: Success, approval, positive outcomes
- red / rose: Errors, danger, destructive actions
- orange / amber / yellow: Warnings, caution, in-progress states
- purple / violet / pink: Brand identity, creative/joyful tone, decorative emphasis
- brown: Neutral labels/backgrounds, secondary information emphasis
- slate / zinc / gray / neutral: Neutral text and surfaces, outlines, separators
- white / black: Baseline for very light/very dark surfaces and text
- Regular Style:
- Primary Colors: blue, pink, purple, green, orange, yellow, sky, red, brown, gray, neutral, black, white, teal, cyan, indigo, violet, lime, emerald, amber, rose, slate, zinc
- Application Level (regularApplicationColors): accent (brand accents), primary (primary interactions), secondary (secondary interaction)
- Semantic Level (regularElementColors / regularBackgroundColors / regularFillColors / regularMaterialColors):
- text.*, border.*, separator.primary, link.primary, disabled*, background.* (primary→quinary), fill.* (primary→quaternary), material.* (ultraThick→opaque)
- Grayscale (regularGrayScale): gray1→gray10 supporting neutral surfaces and contrast levels
- Kawaii Style:
- Distinctive features: More refined text/border contrast control on light color palettes; softer background/fill gradations
- Application Level (kawaiiApplicationColors): accent/primary/secondary follow Regular roles, with softer hue and luminance
- Semantic Level (kawaiiElementColors, etc.): links lean toward soft blue/cyan; disabled* aligns with overall color temperature
- Grayscale (kawaiiGrayScale): consistent gradation with Regular, adapted to overall style
- High Contrast Style:
- Distinctive features: text is darker (light)/lighter (dark); links have higher saturation; background/fill spans a wider range; material elements exhibit stronger contrast
- Application Level (highContrastApplicationColors): accent/primary/secondary colors presented with higher contrast
- Semantic Level (highContrastElementColors, etc.): All semantic layers optimized under high-contrast constraints
- Grayscale (highContrastGrayScale): Same gradient as Regular but with overall stronger contrast
## Docs
- [Demo Documentation Site](https://pastel.innei.dev/): Interactive preview of all themes and colors
## Optional
- [packages/colors source code (theme definitions)](https://github.com/Innei/pastel/tree/main/packages/colors/src): Theme and color definitions, including Regular/Kawaii/High Contrast
- [docs site source code](https://github.com/Innei/pastel/tree/main/docs): Example and preview site
- [Tailwind Theme Export (packages/tailwindcss-colors)](https://github.com/Innei/pastel/tree/main/packages/tailwindcss-colors): Generator and theme CSS export

View File

@@ -1,70 +0,0 @@
# Discover Modal Architecture
This module follows the shared [Store + Actions pattern](./store-actions-pattern.md) so the data flow stays predictable and easy to reason about. The notes below capture Discover-specific nuances; refer to the general guide for broader expectations.
## High-level flow
```
UI component -> DiscoverModalActions.<action>() -> DiscoverService/API -> store.ts state update -> UI selector re-render
```
- `store.ts` exposes a Zustand store that only holds raw state (provider metadata, form inputs, query results, selection, preview, importing flags). It **must not** contain business logic, async calls, or toast usage.
- `actions/index.ts` exports the `DiscoverModalActions` singleton. Feature-specific logic lives in `actions/slices/*`, while `actions/context.ts` manages shared helpers (tokens, persistence, helpers for history). Slices pull snapshots via `discoverModalStore.getState()`, call `DiscoverService` (or other modules like `TorrentActions`), then push state updates with `discoverModalStore.setState()`. Every async action should safeguard against race conditions with request tokens when applicable.
- UI components subscribe with selectors (`useDiscoverModalStore(state => state.xxx)`) for fine-grained updates and call `DiscoverModalActions.shared.<method>()` on user events.
## Store responsibilities
`DiscoverModalState` should include only primitive state values:
- Provider context: `activeProviderId`, `providerReady`, `pageSize`, filter definitions, default filters.
- Form + search state: `keyword`, `filters`, `committedSearch`, `items`, `total`, `hasMore`, `isSearching`, `searchError`.
- Selection & preview: `selectedIds`, `previewId`, `previewDetail`, `isPreviewLoading`, `previewError`.
- Import flag: `importing`.
Setter helper methods (e.g. `setKeyword`) live outside the store, inside actions. The store should only expose `getState()`/`setState()` via the `discoverModalStore` helper.
## Actions responsibilities
Each action method should:
1. Grab a snapshot (`discoverModalStore.getState()`).
2. Early-return with a typed `ActionResult` when a precondition fails (e.g. provider not ready).
3. Use `discoverModalStore.setState()` to apply optimistic updates before a request.
4. Call `DiscoverService`/`TorrentActions` and handle the response.
5. Guard against outdated promises with `this.searchToken`/`this.previewToken` style counters when fetching search or preview data.
6. Return `{ ok: boolean, error?: string }` so the caller decides how to render feedback (toast, UI message, etc.).
Do **not** emit toast notifications inside actions; keep user-facing feedback in the UI layer.
### Common actions
- `configureProvider` resets the store when the provider changes and drops in default filters.
- `performSearch` and `goToPage` share the `fetchPage` helper and manage search concurrency tokens.
- `toggleSelection` and `selectAll` adjust the selection set and delegate preview management to `setPreview`.
- `setPreview` triggers preview fetching and handles stale responses with `previewToken`.
- `importSelected`/`importPreview` wrap download URL resolution and torrent submission.
## UI guidelines
- Always subscribe with selectors to avoid unnecessary re-renders (`useDiscoverModalStore(state => state.items)` instead of destructuring the full store).
- All user interactions should call an action; never mutate store state directly from a component.
- When actions return `error` codes, map them to localized messages in the component (e.g. `providerNotReady`, `requestFailed`, `selectionEmpty`).
- Use the existing i18n keys: `discover.messages.searchFailed`, `discover.messages.previewFailed`, `discover.messages.importFailed`, `discover.messages.importSuccess`, `discover.messages.providerNotReady`.
## Extending the modal
When adding new provider-specific filters or data fields:
1. Update the provider implementation and its `getFilterDefinitions` method.
2. Ensure `configureProvider` receives the new defaults.
3. Extend the store state/interface if you need additional persisted values.
4. Add actions or extend existing ones for the new behavior.
5. Update UI components to read from selectors and surface the new data.
## Testing checklist
- Run `pnpm typecheck:turbo` after any change.
- Manually verify: provider switching, initial filter defaults, searching, pagination, selection, preview loading, single/bulk import.
- Confirm optimistic state transitions (e.g. clearing selection after import) behave as expected without relying on a toast to hide issues.
Following this pattern keeps the Discover modal consistent with the rest of the app and makes it easier to share logic with automation agents.

View File

@@ -1,46 +0,0 @@
# Store + Actions Pattern
Use this pattern for any complex UI module (modals, dashboards, wizards, etc.) that needs shared state, async side-effects, and reusable logic. The Discover modal is the reference implementation—see `docs/discover-modal-architecture.md` for a concrete example.
## Principles
1. **Single source of truth** — A Zustand store (`store.ts`) holds only serializable state. It defines the initial shape and exposes `getState`/`setState`. Do not put derived data, async calls, or business rules into the store definition.
2. **Global singleton actions** — A `*Actions` class (e.g. `DiscoverModalActions`) encapsulates every side-effect: service calls, optimistic updates, selection logic, and concurrency guards. Export a single instance (usually `shared`) so the same logic works from UI, background tasks, or agents.
3. **Selectors in UI** — Components subscribe with selectors (`useStore(state => state.slice)`) for fine-grained re-renders, then invoke actions directly on user events. Components never mutate the store themselves.
4. **Result-based feedback** — Actions return `{ ok, data?, error? }`. UI surfaces messages (toast, inline banner) based on the `error` code, keeping rendering concerns out of the actions.
## Implementation checklist
- `store.ts`
- Export `useXStore` via `createWithEqualityFn + subscribeWithSelector + immer`.
- Provide a helper (`xStore`) exposing `getState` / `setState` / `reset` for actions to use.
- Store only raw values: inputs, fetched results, flags. Derive display values in selectors or components.
- `actions/`
- `index.ts` stitches together feature-specific slices and exposes the singleton.
- `slices/*` group related side-effects (provider, form, search, history, selection, preview, importing).
- Shared helpers live in `actions/context.ts` (tokens, persistence) and `actions/utils.ts` (pure utilities).
- Use request tokens when loading paginated or preview data to avoid stale updates.
- Keep user-facing strings out of slices; return symbolic `error` keys instead.
- Expose imperative helpers (`configure`, `search`, `goToPage`, `toggleSelection`, `importSelected`, etc.).
- UI components
- Select minimal slices from the store.
- Call actions inside handlers (`onClick={() => DiscoverModalActions.shared.search()}`).
- Map returned `error` keys to localized messages.
- Avoid prop drilling; each component subscribes to the pieces it needs.
## When to apply
Adopt this pattern whenever a feature needs:
- Shared state across multiple components.
- Async workflows (search, preview, import, etc.).
- Logic reuse outside React (automation, background tasks).
- Deterministic control over optimistic updates and race conditions.
For simple components, local `useState` is fine. Once the feature spans multiple files or requires cross-component coordination, migrate to this pattern.
## Further reading
- `docs/discover-modal-architecture.md` — Full reference implementation with search, pagination, preview, and import flows.

View File

@@ -21,7 +21,7 @@ interface FeaturedGallery {
author: FeaturedGalleryAuthor | null
photoCount: number
tags: string[]
createdAt: string
lastUpload: string
}
interface FeaturedGalleriesResponse {
@@ -241,7 +241,7 @@ export const GalleryShowcase = () => {
{/* Footer */}
<div className="flex items-center justify-between">
<div className="text-xs text-white/40">
{formatDate(gallery.createdAt)}
{t('lastUpload')} {formatDate(gallery.lastUpload)}
</div>
<div className="text-white/30 transition group-hover:text-white/60">
<i className="i-lucide-external-link size-4" />

View File

@@ -184,6 +184,7 @@
"title": "Explore registered visual spaces",
"description": "Discover amazing photography archives created by other photographers and curators, experience different visual storytelling styles.",
"error": "Failed to load gallery list, please try again later.",
"empty": "No registered galleries yet."
"empty": "No registered galleries yet.",
"lastUpload": "Last upload"
}
}
}

View File

@@ -184,6 +184,7 @@
"title": "探索已注册的影像空间",
"description": "发现其他摄影师和策展人创建的精彩影像档案馆,感受不同的视觉叙事风格。",
"error": "加载画展列表时出错,请稍后重试。",
"empty": "暂无已注册的画展。"
"empty": "暂无已注册的画展。",
"lastUpload": "最近上传"
}
}
}

View File

@@ -108,7 +108,7 @@ export class FeaturedGalleriesService {
const finalTenantIds = validTenants.map((t) => t.id)
// Step 3: Fetch all related data in parallel
const [siteSettings, authors, domains] = await Promise.all([
const [siteSettings, authors, domains, lastUpdatedRows] = await Promise.all([
// Site settings
db
.select()
@@ -137,6 +137,17 @@ export class FeaturedGalleriesService {
})
.from(tenantDomains)
.where(and(inArray(tenantDomains.tenantId, finalTenantIds), eq(tenantDomains.status, 'verified'))),
// Last photo library update time per tenant
db
.select({
tenantId: photoAssets.tenantId,
lastUpdatedAt: sql<Date>`max(${photoAssets.updatedAt})`,
})
.from(photoAssets)
.where(
and(inArray(photoAssets.tenantId, finalTenantIds), inArray(photoAssets.syncStatus, ['synced', 'conflict'])),
)
.groupBy(photoAssets.tenantId),
])
// Step 4: Fetch popular tags for top tenants (batch query)
@@ -188,12 +199,17 @@ export class FeaturedGalleriesService {
}
const domainMap = new Map<string, string>()
const lastUpdatedMap = new Map<string, Date | null>()
for (const domain of domains) {
if (!domainMap.has(domain.tenantId)) {
domainMap.set(domain.tenantId, domain.domain)
}
}
for (const row of lastUpdatedRows) {
lastUpdatedMap.set(row.tenantId, row.lastUpdatedAt)
}
// Step 6: Build response sorted by quality score
const featuredGalleries = validTenants
.map((tenant) => {
@@ -217,7 +233,11 @@ export class FeaturedGalleriesService {
: null,
photoCount: score?.photoCount ?? 0,
tags,
createdAt: normalizeDate(tenant.createdAt) ?? tenant.createdAt,
createdAt: normalizeDate(tenant.createdAt),
lastUpload:
normalizeDate(lastUpdatedMap.get(tenant.id) ?? undefined) ??
lastUpdatedMap.get(tenant.id) ??
normalizeDate(tenant.createdAt),
}
})
.filter((gallery) => gallery.photoCount > 0)