Add archived/atomk-desktop-sidebar
This commit is contained in:
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
name: atomk-desktop-sidebar
|
||||||
|
description: Manage AtomK Desktop sidebar menu — add/remove/rename/group items, i18n syncing, QuickPromptBar readonly, Prompts CRUD page
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# AtomK Desktop Sidebar Menu Management
|
||||||
|
|
||||||
|
Guide for adding, removing, renaming, or reordering sidebar menu items in AtomK Desktop (Electron app).
|
||||||
|
|
||||||
|
## Session-specific references
|
||||||
|
|
||||||
|
- `references/atomlisting-files-media-api.md` — Files menu placement, AtomListing `/api/v1/media/*` endpoints, storage-space UI requirements, and IPC/preload wiring pattern.
|
||||||
|
|
||||||
|
## File Map (all changes touch these files)
|
||||||
|
|
||||||
|
| File | What to change |
|
||||||
|
|------|---------------|
|
||||||
|
| `src/renderer/src/screens/Layout/Layout.tsx` | View type union, NAV_ENTRIES array, NavCollapsible logic, rendering JSX, imports |
|
||||||
|
| `src/shared/i18n/locales/{zh-CN,en}/navigation.ts` | Add/remove/rename the `key: "label"` entry |
|
||||||
|
| `src/renderer/src/screens/{ScreenName}/{ScreenName}.tsx` | New screen component (if adding) |
|
||||||
|
| `src/renderer/src/assets/main.css` | New styles (if needed) |
|
||||||
|
|
||||||
|
## Current Menu Structure (3 flat groups with dividers)
|
||||||
|
|
||||||
|
```
|
||||||
|
Group 1 — 核心: Chat / Sessions / Prompts / Skills / Tools
|
||||||
|
Group 2 — 业务: Accounts / Products / Listings / Posts / Files / Mail ← Accounts page has tabs (Stores | Social); Products/Listings/Posts/Files/Mail use AtomListing API
|
||||||
|
Group 3 — 系统: Schedules / Cloud Bridge / Settings
|
||||||
|
```
|
||||||
|
|
||||||
|
**User-specified ordering** (do not rearrange without explicit request):
|
||||||
|
- Sessions comes right after Chat in Group 1
|
||||||
|
- Accounts is the FIRST item in Group 2
|
||||||
|
- Products → Listings → Posts → Files → Mail are grouped together in Group 2
|
||||||
|
- Files belongs in the business group between Posts and Mail, not in the core group, because it connects to atomlisting.com APIs
|
||||||
|
|
||||||
|
**Business group note**: Products, Listings, Posts, Files, and Mail call `window.hermesAPI.atomListing.*` IPC → `atomlisting.ts` → `https://www.atomlisting.com`. Files must use AtomListing's media library API, not a placeholder/local file browser: `getFiles()` → IPC `atomlisting-files-list` → `atomkAPI.getFiles()` → `GET /api/v1/media/files/`; `getFileFolders()` → `GET /api/v1/media/files/folders`; `getMediaOverview()` → `GET /api/v1/media/` for storage-space/quota. Normalize common response shapes (`items`, `data`, `files`, or array) and tolerate multiple storage field names. The user wants these AtomListing-backed items grouped together.
|
||||||
|
|
||||||
|
**Important UX decision**: Accounts does NOT use a collapsible sidebar group. Instead, it navigates to a dedicated Accounts page with internal tabs for "Stores" and "Social Accounts". The user explicitly prefers: "不是在菜单里折叠 而是在右侧主体框里分两个不同区域显示".
|
||||||
|
|
||||||
|
## Nav Data Model (Layout.tsx)
|
||||||
|
|
||||||
|
The sidebar uses `NAV_GROUPS: NavGroup[]` — an array of flat groups:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface NavItem { view: View; icon: LucideIcon; labelKey: string; }
|
||||||
|
interface NavGroup { items: NavItem[]; }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Historical note**: A `NavCollapsible` type with `parentLabelKey`/`children`/`childViews`/`items` was previously used for the Accounts group, but was removed in favor of a flat "Accounts" nav item that opens a tabbed page. The `NavCollapsible` interface and `isCollapsible()` type guard still exist in Layout.tsx but are unused.
|
||||||
|
|
||||||
|
## Step-by-step: Add a new menu item (flat)
|
||||||
|
|
||||||
|
1. **Create screen component** at `src/renderer/src/screens/{Name}/{Name}.tsx`:
|
||||||
|
```tsx
|
||||||
|
import { IconName } from "lucide-react";
|
||||||
|
import { useI18n } from "../../components/useI18n";
|
||||||
|
function Name(): React.JSX.Element {
|
||||||
|
const { t } = useI18n();
|
||||||
|
return (
|
||||||
|
<div className="screen-container">
|
||||||
|
<div className="screen-header">
|
||||||
|
<IconName size={20} />
|
||||||
|
<h2>{t("navigation.key")}</h2>
|
||||||
|
</div>
|
||||||
|
<div className="screen-body">
|
||||||
|
<p className="screen-placeholder">{t("common.comingSoon")}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Name;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add View type** — append `"name"` to the `View` union type in Layout.tsx
|
||||||
|
|
||||||
|
3. **Add to NAV_ENTRIES** — for a flat item, insert into the appropriate `NavGroup`'s `items` array:
|
||||||
|
`{ view: "name", icon: IconComponent, labelKey: "navigation.name" }`
|
||||||
|
|
||||||
|
4. **Add import** — import the icon from `lucide-react` AND the screen component.
|
||||||
|
- **Icon import pitfall**: Some icons (e.g. `ChatBubble`, `Settings`, `Trash2`) are re-exported with aliases from `../../assets/icons`. Check `src/renderer/src/assets/icons/index.tsx` first — if already re-exported there, import from there. Otherwise import directly from `lucide-react`. **Never import the same icon from both paths** — duplicate identifiers cause TS errors.
|
||||||
|
|
||||||
|
5. **Add rendering block** in Layout.tsx:
|
||||||
|
```tsx
|
||||||
|
{visitedViews.has("name") && (
|
||||||
|
<div style={paneStyle("name")}>
|
||||||
|
<Name />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Update 2 locale files** — add `name: "Label"` to each `src/shared/i18n/locales/{en,zh-CN}/navigation.ts`
|
||||||
|
|
||||||
|
7. **Add CSS** if needed (use existing `.screen-container/header/body/placeholder` classes for placeholder pages)
|
||||||
|
|
||||||
|
8. **Verify**: `npx tsc --noEmit` — must be zero errors
|
||||||
|
|
||||||
|
9. **Git**: `git add -A && git commit -m "..." && git push atomk HEAD:refs/heads/main --force`
|
||||||
|
|
||||||
|
## Step-by-step: Add a collapsible group
|
||||||
|
|
||||||
|
Collapsible groups (like "账号/Accounts") contain a parent row that expands/collapses to reveal child items.
|
||||||
|
|
||||||
|
1. **Add all child View types** to the `View` union (same as flat items above)
|
||||||
|
|
||||||
|
2. **Define a NavCollapsible entry** in `NAV_ENTRIES`:
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
parentLabelKey: "navigation.accounts",
|
||||||
|
parentIcon: Users,
|
||||||
|
children: [
|
||||||
|
{ view: "stores", icon: Store, labelKey: "navigation.stores" },
|
||||||
|
{ view: "social", icon: Share2, labelKey: "navigation.social" },
|
||||||
|
],
|
||||||
|
childViews: ["stores", "social"],
|
||||||
|
// Optional: flat sibling items that share the visual group (no divider)
|
||||||
|
items: [
|
||||||
|
{ view: "products", icon: Package, labelKey: "navigation.products" },
|
||||||
|
],
|
||||||
|
} satisfies NavCollapsible,
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Rendering** — Layout.tsx iterates `NAV_ENTRIES` and uses `isCollapsible()` to switch rendering:
|
||||||
|
- Collapsible: renders `.sidebar-nav-parent` (click toggles group) + `.sidebar-nav-children` (animated expand) + optional `entry.items?.map(...)` for flat siblings below
|
||||||
|
- Flat group: renders individual `.sidebar-nav-item` buttons
|
||||||
|
|
||||||
|
4. **State** — `expandedGroups: Set<string>` tracks which groups are open (keyed by `parentLabelKey`). Auto-expanded when navigating to a child view.
|
||||||
|
|
||||||
|
5. **CSS classes**:
|
||||||
|
- `.sidebar-nav-collapsible` — wrapper div
|
||||||
|
- `.sidebar-nav-parent` — clickable parent row (style matches `.sidebar-nav-item`)
|
||||||
|
- `.sidebar-nav-parent.has-active-child` — highlight when a child is active
|
||||||
|
- `.sidebar-nav-chevron` + `.rotated` — chevron icon rotates 90deg when expanded
|
||||||
|
- `.sidebar-nav-children` + `.open` — uses `grid-template-rows: 0fr -> 1fr` animation
|
||||||
|
- `.sidebar-nav-child` — child items with `padding-left: 44px` indentation
|
||||||
|
|
||||||
|
6. **Icon import**: `ChevronRight` comes from `lucide-react` directly (not re-exported via assets/icons).
|
||||||
|
|
||||||
|
## Step-by-step: Remove a menu item
|
||||||
|
|
||||||
|
1. Remove from `NAV_ENTRIES` array (from the relevant `NavGroup.items` or `NavCollapsible.children`)
|
||||||
|
2. Remove from `View` type union
|
||||||
|
3. Remove the rendering block (`visitedViews.has(...)` div)
|
||||||
|
4. Remove the screen component import
|
||||||
|
5. Keep the component FILE (don't delete — avoids breaking other imports)
|
||||||
|
6. Remove `key: "..."` from both `src/shared/i18n/locales/{en,zh-CN}/navigation.ts`
|
||||||
|
7. Verify + commit
|
||||||
|
|
||||||
|
## Step-by-step: Rename a menu item
|
||||||
|
|
||||||
|
1. Change the label string in both `src/shared/i18n/locales/{en,zh-CN}/navigation.ts`
|
||||||
|
2. No code changes needed if only display text changes (labelKey stays the same)
|
||||||
|
|
||||||
|
## Step-by-step: Add a group divider
|
||||||
|
|
||||||
|
Groups are defined in `NAV_ENTRIES` array. Flat `NavGroup` entries each render as one visible group. Collapsible `NavCollapsible` entries render as a single expandable group with optional flat `items` siblings. Dividers render between entries automatically via `gi > 0 && <div className=\"sidebar-nav-divider\" />`.
|
||||||
|
|
||||||
|
To merge two groups into one: move the flat items from one `NavGroup` into the `items` field of a `NavCollapsible` entry (or into a `NavGroup` that's adjacent). This removes the divider between them.
|
||||||
|
|
||||||
|
To regroup: just move `{ view, icon, labelKey }` objects between `NavGroup` `items` arrays, or restructure as a `NavCollapsible`.
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
|
||||||
|
- **EN navigation label for Mail**: Use `"Mail"` NOT `"Email"`. The user explicitly corrected this. Key = `navigation.mail`.
|
||||||
|
- **Files menu placement**: `navigation.crawler` currently displays as "Files" / "文件" and must live in Group 2 between Posts and Mail. Do not leave it in Group 1. When wiring Files to AtomListing, add all three layers: `atomlisting.ts` API method, `index.ts` IPC handler, and `preload/index.ts` + `preload/index.d.ts` exposure. See `references/atomlisting-media-library.md` for current `/api/v1/media/*` routes, storage-space fields, and normalization rules.
|
||||||
|
|
||||||
|
- **QuickPromptBar** reads from `DEFAULT_QUICK_PROMPTS` + `loadCustomPrompts()` (localStorage key `atomk-custom-prompts`). Add `readonly: true` to make a prompt undeletable/uneditable.
|
||||||
|
- **Prompts management page** at `src/renderer/src/screens/Prompts/Prompts.tsx` groups by `readonly → login → workflow → context`.
|
||||||
|
- **Gateway platform badge** in sidebar: `window.hermesAPI.getPlatformEnabled(profile)` polls every 15s, renders connected platform names after the "通讯" label.
|
||||||
|
- Icons: use `lucide-react` icons. For conflicting names (e.g. `Mail`), alias: `import { Mail as MailIcon }`.
|
||||||
|
- Alias `Settings` too: `import { Settings as SettingsIcon }`.
|
||||||
|
|
||||||
|
## Git Push
|
||||||
|
|
||||||
|
Remote is `atomk` → `https://gitea9webs.sh3.ikuai7.com/admin9webs/AtomK-Desktop.git`, branch `main`.
|
||||||
|
Force push often needed: `git push atomk main --force`
|
||||||
|
Note: The old `clean-main` branch was consolidated into `main` — only `main` branch exists now on Gitea. The `gitea` remote was removed due to timeout issues.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- Don't forget BOTH locales (zh-CN, en) or you'll get runtime i18n key-not-found
|
||||||
|
- After adding a new icon import, make sure no name collision with existing imports (use aliases)
|
||||||
|
- `screen-body` default is centered — add `.prompts-body` override class if you need flex-start layout with scroll
|
||||||
|
- **Icon dual-import pitfall**: `src/renderer/src/assets/icons/index.tsx` re-exports some `lucide-react` icons with aliases (e.g. `ChatBubble = MessageSquare`, `Settings`, `Trash2 = Trash`). If an icon is there, import from `../../assets/icons`; otherwise from `lucide-react` directly. **Never both** — duplicate identifiers fail `tsc`. When in doubt, `grep` the icons/index.tsx first.
|
||||||
|
- `ChevronRight` is NOT re-exported via assets/icons — always import directly from `lucide-react`.
|
||||||
|
- **⚠️ Navigation items silently dropped during Layout.tsx edits**: When modifying Layout.tsx (e.g. removing items, reordering groups, or merging branches), it's easy to accidentally drop unrelated nav items. **Always verify after Layout.tsx changes**: compare the `NAV_GROUPS` array against `src/renderer/src/screens/` directory — every screen folder with a default-exported component should have a corresponding nav entry (unless intentionally hidden). The i18n `navigation.ts` files are a good checklist: if a key exists there but has no `NAV_GROUPS` entry, something was lost. **This pitfall has recurred at least 4 times independently** (Gateway/Messaging removal, Products/Listings/Posts/Mail, Sessions, and during group reordering). **Mandatory step**: after ANY Layout.tsx edit, run `ls src/renderer/src/screens/` and compare against `NAV_GROUPS` — if a screen folder has no nav entry, add it back.
|
||||||
Reference in New Issue
Block a user