first commit
Some checks failed
Backend Tests / Static Checks (push) Has been cancelled
Backend Tests / Tests (other) (push) Has been cancelled
Backend Tests / Tests (plugin) (push) Has been cancelled
Backend Tests / Tests (server) (push) Has been cancelled
Backend Tests / Tests (store) (push) Has been cancelled
Build Canary Image / build-frontend (push) Has been cancelled
Build Canary Image / build-push (linux/amd64) (push) Has been cancelled
Build Canary Image / build-push (linux/arm64) (push) Has been cancelled
Build Canary Image / merge (push) Has been cancelled
Frontend Tests / Lint (push) Has been cancelled
Frontend Tests / Build (push) Has been cancelled
Proto Linter / Lint Protos (push) Has been cancelled
Some checks failed
Backend Tests / Static Checks (push) Has been cancelled
Backend Tests / Tests (other) (push) Has been cancelled
Backend Tests / Tests (plugin) (push) Has been cancelled
Backend Tests / Tests (server) (push) Has been cancelled
Backend Tests / Tests (store) (push) Has been cancelled
Build Canary Image / build-frontend (push) Has been cancelled
Build Canary Image / build-push (linux/amd64) (push) Has been cancelled
Build Canary Image / build-push (linux/arm64) (push) Has been cancelled
Build Canary Image / merge (push) Has been cancelled
Frontend Tests / Lint (push) Has been cancelled
Frontend Tests / Build (push) Has been cancelled
Proto Linter / Lint Protos (push) Has been cancelled
This commit is contained in:
658
frontend.txt
Normal file
658
frontend.txt
Normal file
@@ -0,0 +1,658 @@
|
||||
# Memos Frontend Documentation
|
||||
|
||||
## Authentication and Middleware System
|
||||
|
||||
### Frontend Authentication Architecture
|
||||
|
||||
The frontend authentication system uses a combination of:
|
||||
|
||||
1. **React Context** for state management
|
||||
2. **Route-based guards** for access control
|
||||
3. **Connect RPC interceptors** for API authentication
|
||||
4. **Layout-based protection** for UI components
|
||||
|
||||
### Core Authentication Components
|
||||
|
||||
#### 1. Auth Context (`web/src/contexts/AuthContext.tsx`)
|
||||
|
||||
```typescript
|
||||
// Manages global authentication state
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
currentUser: undefined,
|
||||
userGeneralSetting: undefined,
|
||||
isInitialized: false,
|
||||
isLoading: true,
|
||||
initialize: async () => {},
|
||||
logout: () => {},
|
||||
refetchSettings: async () => {}
|
||||
});
|
||||
|
||||
// Provides authentication state to entire app
|
||||
function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
// Handles token initialization, user fetching, logout logic
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Route Protection System
|
||||
|
||||
**Public Routes** (no auth required):
|
||||
|
||||
- `/auth` - Authentication pages
|
||||
- `/auth/signup` - User registration
|
||||
- `/auth/callback` - OAuth callback
|
||||
- `/explore` - Public explore page
|
||||
- `/u/:username` - User profiles
|
||||
- `/memos/:uid` - Individual memo details
|
||||
|
||||
**Private Routes** (auth required):
|
||||
|
||||
- `/` (root) - Main dashboard
|
||||
- `/attachments` - File attachments
|
||||
- `/inbox` - Notifications
|
||||
- `/archived` - Archived memos
|
||||
- `/setting` - User settings
|
||||
|
||||
#### 3. Layout-Based Authentication Guards
|
||||
|
||||
**RootLayout** (`web/src/layouts/RootLayout.tsx`):
|
||||
|
||||
```typescript
|
||||
const RootLayout = () => {
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser) {
|
||||
redirectOnAuthFailure(); // Redirects to /auth
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
// Renders navigation and main content
|
||||
return (
|
||||
<div className="w-full min-h-full flex flex-row">
|
||||
<Navigation />
|
||||
<main>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**Route Configuration** (`web/src/router/index.tsx`):
|
||||
|
||||
```typescript
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <App />, // Handles instance initialization
|
||||
children: [
|
||||
{
|
||||
path: "/auth", // Public routes
|
||||
children: [
|
||||
{ path: "", element: <SignIn /> },
|
||||
{ path: "signup", element: <SignUp /> },
|
||||
{ path: "callback", element: <AuthCallback /> }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/", // Protected routes (wrapped in RootLayout)
|
||||
element: <RootLayout />, // Auth guard here
|
||||
children: [
|
||||
{
|
||||
element: <MainLayout />, // Main app layout
|
||||
children: [
|
||||
{ path: "", element: <Home /> },
|
||||
{ path: "explore", element: <Explore /> }
|
||||
// ... other protected routes
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
### API Authentication Interceptors
|
||||
|
||||
#### Connect RPC Interceptor (`web/src/connect.ts`):
|
||||
|
||||
```typescript
|
||||
const authInterceptor: Interceptor = (next) => async (req) => {
|
||||
const isRetryAttempt = req.header.get(RETRY_HEADER) === RETRY_HEADER_VALUE;
|
||||
const token = await getRequestToken();
|
||||
setAuthorizationHeader(req, token);
|
||||
|
||||
try {
|
||||
return await next(req);
|
||||
} catch (error) {
|
||||
// Handle 401 Unauthorized
|
||||
if (error.code === Code.Unauthenticated && !isRetryAttempt) {
|
||||
try {
|
||||
// Attempt token refresh
|
||||
const newToken = await refreshAndGetAccessToken();
|
||||
setAuthorizationHeader(req, newToken);
|
||||
req.header.set(RETRY_HEADER, RETRY_HEADER_VALUE);
|
||||
return await next(req);
|
||||
} catch (refreshError) {
|
||||
redirectOnAuthFailure(); // Redirect to login
|
||||
throw refreshError;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### Token Management
|
||||
|
||||
- **Storage**: Access tokens stored in memory (not localStorage)
|
||||
- **Refresh**: Automatic token refresh on 401 errors
|
||||
- **Expiration**: Proactive refresh on tab focus
|
||||
- **Cleanup**: Tokens cleared on logout/auth failure
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
#### 1. App Initialization (`web/src/main.tsx`)
|
||||
|
||||
```typescript
|
||||
// Early theme/locale setup to prevent flash
|
||||
applyThemeEarly();
|
||||
applyLocaleEarly();
|
||||
|
||||
// Initialize auth and instance contexts
|
||||
<AuthProvider>
|
||||
<InstanceProvider>
|
||||
<RouterProvider router={router} />
|
||||
</InstanceProvider>
|
||||
</AuthProvider>
|
||||
```
|
||||
|
||||
#### 2. Authentication Check (`web/src/utils/auth-redirect.ts`)
|
||||
|
||||
```typescript
|
||||
export function redirectOnAuthFailure(): void {
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
// Allow public routes
|
||||
if (isPublicRoute(currentPath)) return;
|
||||
|
||||
// Redirect private routes to auth
|
||||
if (isPrivateRoute(currentPath)) {
|
||||
clearAccessToken();
|
||||
window.location.replace(ROUTES.AUTH);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. User Session Management
|
||||
|
||||
- Token refresh on window focus
|
||||
- Automatic logout on token expiration
|
||||
- Context cleanup on logout
|
||||
- Query cache invalidation
|
||||
|
||||
### Key Security Features
|
||||
|
||||
- **Token Storage**: In-memory only (security best practice)
|
||||
- **Automatic Refresh**: Handles token rotation seamlessly
|
||||
- **Route Guards**: Prevent unauthorized access to protected routes
|
||||
- **Context Isolation**: Auth state managed centrally
|
||||
- **Error Handling**: Graceful degradation on auth failures
|
||||
- **Session Cleanup**: Complete state reset on logout
|
||||
|
||||
### Related Files
|
||||
|
||||
- `web/src/contexts/AuthContext.tsx` - Authentication state management
|
||||
- `web/src/layouts/RootLayout.tsx` - Main authentication guard
|
||||
- `web/src/utils/auth-redirect.ts` - Route protection logic
|
||||
- `web/src/connect.ts` - API authentication interceptors
|
||||
- `web/src/router/index.tsx` - Route configuration
|
||||
- `web/src/main.tsx` - App initialization
|
||||
|
||||
## Theme System Implementation
|
||||
|
||||
### Vite Build Process for Themes
|
||||
|
||||
The CSS themes are processed and built by **Vite** during the build process:
|
||||
|
||||
1. **Vite Configuration** (`web/vite.config.mts`)
|
||||
- Uses `@tailwindcss/vite` plugin for CSS processing
|
||||
- Tailwind CSS v4 handles theme token compilation
|
||||
- Themes are bundled during `pnpm build` process
|
||||
|
||||
2. **CSS Processing Pipeline**
|
||||
- Base styles imported in `web/src/index.css`
|
||||
- Theme-specific CSS files located in `web/src/themes/`
|
||||
- Vite processes `@theme inline` directives at build time
|
||||
- Dynamic theme switching handled via JavaScript at runtime
|
||||
|
||||
### Theme Architecture
|
||||
|
||||
#### Core Theme Files
|
||||
|
||||
- `web/src/index.css` - Main CSS entry point
|
||||
- `web/src/themes/default.css` - Base theme with Tailwind token mappings
|
||||
- `web/src/themes/default-dark.css` - Dark theme variables
|
||||
- `web/src/themes/paper.css` - Paper-style theme
|
||||
- `web/src/utils/theme.ts` - Theme loading and management logic
|
||||
|
||||
#### How Themes Are Built
|
||||
|
||||
##### 1. Build Time Processing (Vite + Tailwind)
|
||||
|
||||
```css
|
||||
/* In web/src/themes/default.css */
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
/* ... other CSS variables */
|
||||
}
|
||||
```
|
||||
|
||||
- Tailwind compiles these into static CSS classes
|
||||
- Shared across all themes
|
||||
- Optimized during Vite build process
|
||||
|
||||
##### 2. Runtime Theme Switching
|
||||
|
||||
- JavaScript dynamically injects theme CSS
|
||||
- Uses `?raw` import to get CSS as string
|
||||
- Injects `<style>` elements into document head
|
||||
- Controlled by `web/src/utils/theme.ts`
|
||||
|
||||
### Theme Loading Mechanism
|
||||
|
||||
In `web/src/utils/theme.ts`:
|
||||
|
||||
```typescript
|
||||
import defaultDarkThemeContent from "../themes/default-dark.css?raw";
|
||||
import paperThemeContent from "../themes/paper.css?raw";
|
||||
|
||||
const THEME_CONTENT: Record<ResolvedTheme, string | null> = {
|
||||
default: null, // Uses base CSS
|
||||
"default-dark": defaultDarkThemeContent,
|
||||
paper: paperThemeContent,
|
||||
};
|
||||
|
||||
// Dynamically injects theme CSS
|
||||
const injectThemeStyle = (theme: ResolvedTheme): void => {
|
||||
if (theme === "default") return; // Use base CSS
|
||||
|
||||
const css = THEME_CONTENT[theme];
|
||||
if (css) {
|
||||
const style = document.createElement("style");
|
||||
style.id = "instance-theme";
|
||||
style.textContent = css;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Available Themes
|
||||
|
||||
1. **System** (`system`) - Follows OS preference
|
||||
2. **Light** (`default`) - Default light theme
|
||||
3. **Dark** (`default-dark`) - Dark mode theme
|
||||
4. **Paper** (`paper`) - Paper-style theme
|
||||
|
||||
### Theme Selection Components
|
||||
|
||||
- `web/src/components/ThemeSelect.tsx` - Theme dropdown selector
|
||||
- `web/src/pages/UserSetting.tsx` - User profile theme settings
|
||||
- `web/src/contexts/InstanceContext.tsx` - Instance-wide theme defaults
|
||||
|
||||
### Build Process Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
pnpm dev
|
||||
|
||||
# Production build (processes themes)
|
||||
pnpm build
|
||||
|
||||
# The build process:
|
||||
# 1. Vite processes Tailwind CSS
|
||||
# 2. Theme CSS files are bundled
|
||||
# 3. Dynamic theme loading code is included
|
||||
# 4. Output goes to dist/ directory
|
||||
```
|
||||
|
||||
### Key Technical Details
|
||||
|
||||
- **CSS-in-JS Approach**: Themes are injected as `<style>` elements
|
||||
- **Tree Shaking**: Unused theme CSS is removed during build
|
||||
- **Hot Reloading**: Theme changes reflect instantly in development
|
||||
- **Performance**: Theme switching is instant (no page reload)
|
||||
- **Storage**: Theme preference stored in localStorage
|
||||
- **Fallback**: Defaults to "system" theme if none selected
|
||||
|
||||
The theme system leverages Vite's build optimization while maintaining runtime flexibility for dynamic theme switching.
|
||||
|
||||
## Menu and Navigation System
|
||||
|
||||
### Navigation Component Architecture
|
||||
|
||||
The menu/navigation system is implemented through the `Navigation` component which serves as the main sidebar navigation.
|
||||
|
||||
### Core Navigation Component (`web/src/components/Navigation.tsx`)
|
||||
|
||||
```typescript
|
||||
interface NavLinkItem {
|
||||
id: string;
|
||||
path: string;
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const Navigation = (props: { collapsed?: boolean; className?: string }) => {
|
||||
const currentUser = useCurrentUser();
|
||||
const { data: notifications = [] } = useNotifications();
|
||||
|
||||
// Navigation items are defined as objects
|
||||
const homeNavLink: NavLinkItem = {
|
||||
id: "header-memos",
|
||||
path: Routes.ROOT,
|
||||
title: t("common.memos"),
|
||||
icon: <LibraryIcon className="w-6 h-auto shrink-0" />
|
||||
};
|
||||
|
||||
// Conditional navigation based on authentication state
|
||||
const navLinks: NavLinkItem[] = currentUser
|
||||
? [homeNavLink, exploreNavLink, attachmentsNavLink, inboxNavLink]
|
||||
: [exploreNavLink, signInNavLink];
|
||||
}
|
||||
```
|
||||
|
||||
### Menu Items Configuration
|
||||
|
||||
#### For Authenticated Users
|
||||
|
||||
1. **Home** (`/`) - Main memos dashboard
|
||||
- Icon: `LibraryIcon`
|
||||
- Shows user's memos
|
||||
|
||||
2. **Explore** (`/explore`) - Public memos exploration
|
||||
- Icon: `EarthIcon`
|
||||
- Browse public content
|
||||
|
||||
3. **Attachments** (`/attachments`) - File management
|
||||
- Icon: `PaperclipIcon`
|
||||
- Manage uploaded files
|
||||
|
||||
4. **Inbox** (`/inbox`) - Notifications
|
||||
- Icon: `BellIcon` with badge
|
||||
- Shows unread notification count
|
||||
|
||||
#### For Unauthenticated Users
|
||||
|
||||
1. **Explore** (`/explore`) - Public content browsing
|
||||
2. **Sign In** (`/auth`) - Authentication page
|
||||
- Icon: `UserCircleIcon`
|
||||
|
||||
### Navigation Layout Structure
|
||||
|
||||
#### Desktop (>768px)
|
||||
|
||||
- Fixed sidebar on left (`w-16` when collapsed, wider when expanded)
|
||||
- `RootLayout` renders `Navigation` component vertically
|
||||
- Collapsed state shows icons only with tooltips
|
||||
- Expanded state shows icons + text labels
|
||||
|
||||
#### Mobile (<768px)
|
||||
|
||||
- `NavigationDrawer` component in mobile header
|
||||
- Slide-out drawer from left side
|
||||
- Full-width navigation when opened
|
||||
|
||||
### Navigation Implementation
|
||||
|
||||
```typescript
|
||||
// In RootLayout.tsx
|
||||
{sm && (
|
||||
<div className="fixed top-0 left-0 h-full w-16">
|
||||
<Navigation className="py-4" collapsed={true} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
// In MobileHeader.tsx
|
||||
<NavigationDrawer /> // For mobile devices
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
#### 1. Conditional Rendering
|
||||
|
||||
- Different menus for authenticated vs unauthenticated users
|
||||
- Notification badges for unread messages
|
||||
- Responsive design for mobile/desktop
|
||||
|
||||
#### 2. Active State Management
|
||||
|
||||
```typescript
|
||||
<NavLink
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
"px-2 py-2 rounded-2xl border flex flex-row",
|
||||
isActive
|
||||
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||
: "border-transparent hover:bg-sidebar-accent"
|
||||
)
|
||||
}
|
||||
to={navLink.path}
|
||||
>
|
||||
```
|
||||
|
||||
#### 3. Collapsed State
|
||||
|
||||
- Icons only with tooltip hints
|
||||
- Space-efficient for narrow screens
|
||||
- Smooth transitions
|
||||
|
||||
#### 4. User Menu Integration
|
||||
|
||||
- Bottom section shows `UserMenu` component
|
||||
- Profile/avatar display
|
||||
- Settings and logout options
|
||||
|
||||
### Related Components
|
||||
|
||||
- `web/src/components/Navigation.tsx` - Main navigation logic
|
||||
- `web/src/components/NavigationDrawer.tsx` - Mobile drawer implementation
|
||||
- `web/src/components/UserMenu.tsx` - User profile dropdown
|
||||
- `web/src/layouts/RootLayout.tsx` - Desktop layout with sidebar
|
||||
- `web/src/components/MobileHeader.tsx` - Mobile header with drawer
|
||||
|
||||
### Internationalization
|
||||
|
||||
- Menu titles translated via `useTranslate()` hook
|
||||
- Supports multiple languages
|
||||
- Dynamic text based on user locale
|
||||
|
||||
The navigation system provides a clean, responsive menu that adapts to user authentication state and screen size while maintaining consistent UX across devices.
|
||||
|
||||
## Layout System (Masonry vs List)
|
||||
|
||||
### View Context Architecture
|
||||
|
||||
The layout system is managed through the `ViewContext` which provides global state management for layout preferences and sorting options.
|
||||
|
||||
### Core View Context (`web/src/contexts/ViewContext.tsx`)
|
||||
|
||||
```typescript
|
||||
export type LayoutMode = "LIST" | "MASONRY";
|
||||
|
||||
interface ViewContextValue {
|
||||
orderByTimeAsc: boolean; // Sort order
|
||||
layout: LayoutMode; // Current layout mode
|
||||
toggleSortOrder: () => void;
|
||||
setLayout: (layout: LayoutMode) => void;
|
||||
}
|
||||
|
||||
// Persistent storage in localStorage
|
||||
const LOCAL_STORAGE_KEY = "memos-view-setting";
|
||||
|
||||
// Default state
|
||||
return { orderByTimeAsc: false, layout: "LIST" as LayoutMode };
|
||||
```
|
||||
|
||||
### Layout Modes
|
||||
|
||||
#### 1. LIST Layout (`"LIST"`)
|
||||
|
||||
- **Description**: Traditional linear list view
|
||||
- **Implementation**: Single column layout
|
||||
- **Behavior**: Memos displayed vertically in chronological order
|
||||
- **Use Case**: Reading-focused, sequential browsing
|
||||
|
||||
#### 2. MASONRY Layout (`"MASONRY"`)
|
||||
|
||||
- **Description**: Pinterest-style grid layout
|
||||
- **Implementation**: Multi-column responsive grid
|
||||
- **Behavior**: Memos distributed based on actual rendered heights
|
||||
- **Use Case**: Visual browsing, efficient space utilization
|
||||
|
||||
### Masonry Layout Implementation
|
||||
|
||||
#### Core Components
|
||||
|
||||
1. **MasonryView** (`web/src/components/MasonryView/MasonryView.tsx`)
|
||||
- Main container component
|
||||
- Manages column distribution
|
||||
- Uses CSS Grid for layout
|
||||
|
||||
2. **MasonryColumn** (`web/src/components/MasonryView/MasonryColumn.tsx`)
|
||||
- Represents individual columns
|
||||
- Contains assigned memos
|
||||
- Handles prefix elements (like memo editor)
|
||||
|
||||
3. **MasonryItem** (`web/src/components/MasonryView/MasonryItem.tsx`)
|
||||
- Wraps individual memos
|
||||
- Measures actual rendered height
|
||||
- Uses ResizeObserver for dynamic updates
|
||||
|
||||
4. **useMasonryLayout** Hook (`web/src/components/MasonryView/useMasonryLayout.ts`)
|
||||
- Calculates optimal column count
|
||||
- Distributes memos to columns
|
||||
- Manages height measurements
|
||||
|
||||
#### Key Features
|
||||
|
||||
##### 1. Height-Based Distribution
|
||||
|
||||
```typescript
|
||||
// Smart algorithm that assigns memos to shortest column
|
||||
const shortestColumnIndex = columnHeights.reduce(
|
||||
(minIndex, currentHeight, currentIndex) =>
|
||||
(currentHeight < columnHeights[minIndex] ? currentIndex : minIndex),
|
||||
0
|
||||
);
|
||||
```
|
||||
|
||||
##### 2. Dynamic Column Count
|
||||
|
||||
```typescript
|
||||
const calculateColumns = useCallback(() => {
|
||||
if (!containerRef.current || listMode) return 1;
|
||||
|
||||
const containerWidth = containerRef.current.offsetWidth;
|
||||
const scale = containerWidth / MINIMUM_MEMO_VIEWPORT_WIDTH;
|
||||
return scale >= 1.2 ? Math.ceil(scale) : 1;
|
||||
}, [containerRef, listMode]);
|
||||
```
|
||||
|
||||
##### 3. ResizeObserver Integration
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
const measureHeight = () => {
|
||||
if (itemRef.current) {
|
||||
const height = itemRef.current.offsetHeight;
|
||||
onHeightChange(memo.name, height);
|
||||
}
|
||||
};
|
||||
|
||||
resizeObserverRef.current = new ResizeObserver(measureHeight);
|
||||
resizeObserverRef.current.observe(itemRef.current);
|
||||
}, [memo.name, onHeightChange]);
|
||||
```
|
||||
|
||||
##### 4. Responsive Behavior
|
||||
|
||||
- Automatically adjusts columns based on viewport width
|
||||
- Minimum width threshold for multi-column layout
|
||||
- Smooth transitions during resizing
|
||||
|
||||
### Layout Switching
|
||||
|
||||
#### User Interface
|
||||
|
||||
```typescript
|
||||
// MemoDisplaySettingMenu component
|
||||
<Select value={layout} onValueChange={(value) => setLayout(value as "LIST" | "MASONRY")}>
|
||||
<SelectItem value="LIST">{t("memo.list")}</SelectItem>
|
||||
<SelectItem value="MASONRY">{t("memo.masonry")}</SelectItem>
|
||||
</Select>
|
||||
```
|
||||
|
||||
#### State Management
|
||||
|
||||
```typescript
|
||||
const { layout, setLayout } = useView();
|
||||
|
||||
// Toggle between layouts
|
||||
setLayout("MASONRY"); // or "LIST"
|
||||
|
||||
// Persistence
|
||||
localStorage.setItem("memos-view-setting", JSON.stringify({ layout, orderByTimeAsc }));
|
||||
```
|
||||
|
||||
### Integration with Memo Display
|
||||
|
||||
#### PagedMemoList Component
|
||||
|
||||
```typescript
|
||||
const PagedMemoList = (props: Props) => {
|
||||
const { layout } = useView(); // Get current layout preference
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
||||
{layout === "MASONRY" ? (
|
||||
<MasonryView
|
||||
memoList={sortedMemoList}
|
||||
renderer={props.renderer}
|
||||
prefixElement={<MemoEditor />}
|
||||
/>
|
||||
) : (
|
||||
// Traditional list view implementation
|
||||
<ListView memoList={sortedMemoList} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Performance Optimizations
|
||||
|
||||
1. **Debounced Redistribution**: Prevents excessive re-layout during rapid changes
|
||||
2. **Memoized Calculations**: Optimized height calculations and distribution algorithms
|
||||
3. **Efficient State Updates**: Only re-render when necessary
|
||||
4. **ResizeObserver Cleanup**: Proper memory management
|
||||
|
||||
### Related Files
|
||||
|
||||
- `web/src/contexts/ViewContext.tsx` - Global layout state management
|
||||
- `web/src/components/MasonryView/` - Masonry layout implementation
|
||||
- `web/src/components/MemoDisplaySettingMenu.tsx` - Layout selection UI
|
||||
- `web/src/components/PagedMemoList/PagedMemoList.tsx` - Memo list container
|
||||
- `web/src/components/MasonryView/README.md` - Detailed technical documentation
|
||||
|
||||
### User Experience Benefits
|
||||
|
||||
- **Choice**: Users can choose preferred browsing style
|
||||
- **Persistence**: Layout preference saved across sessions
|
||||
- **Responsiveness**: Adapts to different screen sizes
|
||||
- **Performance**: Optimized rendering for both modes
|
||||
- **Consistency**: Same memo content, different presentation
|
||||
|
||||
The layout system provides flexible viewing options that cater to different user preferences and use cases while maintaining optimal performance and responsive design.
|
||||
Reference in New Issue
Block a user