Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 80 additions & 5 deletions understand-anything-plugin/packages/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {
}, [setDomainGraph]);

return (
<I18nProvider language={outputLanguage ?? "en"}>
<I18nProvider language={outputLanguage ?? "en"} onLanguageChange={setOutputLanguage}>
<ThemeProvider metaTheme={metaTheme}>
<DashboardContent
accessToken={accessToken}
Expand Down Expand Up @@ -246,6 +246,12 @@ function DashboardContent({
const toggleShowFunctionsInClassView = useDashboardStore((s) => s.toggleShowFunctionsInClassView);
const [showKeyboardHelp, setShowKeyboardHelp] = useState(false);
const [sidebarTab, setSidebarTab] = useState<SidebarTab>("info");
const sidebarPosition = useDashboardStore((s) => s.sidebarPosition);
const setSidebarPosition = useDashboardStore((s) => s.setSidebarPosition);
const sidebarCollapsed = useDashboardStore((s) => s.sidebarCollapsed);
const toggleSidebarCollapsed = useDashboardStore((s) => s.toggleSidebarCollapsed);
const nodeHistory = useDashboardStore((s) => s.nodeHistory);
const goBackNode = useDashboardStore((s) => s.goBackNode);
const [showOnboarding, setShowOnboarding] = useState(shouldShowOnboarding);
const dismissOnboarding = useCallback((remember: boolean) => {
if (remember && typeof window !== "undefined") {
Expand Down Expand Up @@ -420,6 +426,41 @@ function DashboardContent({
{tab === "info" ? t.sidebar.info : t.sidebar.files}
</button>
))}
{/* Sidebar position & collapse controls */}
<div className="flex items-center gap-1 ml-1">
<button
type="button"
onClick={() => setSidebarPosition(sidebarPosition === "right" ? "left" : "right")}
className="p-1 rounded text-text-muted hover:text-text-primary hover:bg-elevated transition-colors"
title={sidebarPosition === "right" ? t.sidebar.moveLeft : t.sidebar.moveRight}
>
{sidebarPosition === "right" ? (
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 19l-7-7 7-7M4 12h16" />
</svg>
) : (
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 5l7 7-7 7M20 12H4" />
</svg>
)}
</button>
<button
type="button"
onClick={toggleSidebarCollapsed}
className="p-1 rounded text-text-muted hover:text-text-primary hover:bg-elevated transition-colors"
title={t.sidebar.collapse}
>
{sidebarPosition === "right" ? (
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
) : (
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
)}
</button>
</div>
</div>
<div className="flex-1 min-h-0 overflow-auto">
{sidebarTab === "files" ? <FileExplorer /> : infoSidebarContent}
Expand Down Expand Up @@ -635,6 +676,38 @@ function DashboardContent({

{/* Main content: Graph + Sidebar */}
<div className="flex-1 flex min-h-0 relative">
{/* Sidebar on the left side */}
{sidebarPosition === "left" && !sidebarCollapsed && (
<aside className="w-[260px] md:w-[300px] lg:w-[360px] shrink-0 bg-surface border-r border-border-subtle overflow-auto">
{sidebarContent}
</aside>
)}
{/* Collapsed sidebar edge button */}
{sidebarCollapsed && sidebarPosition === "left" && (
<button
type="button"
onClick={toggleSidebarCollapsed}
className="absolute top-0 left-0 z-10 h-full w-6 bg-surface border-r border-border-subtle flex items-center justify-center hover:bg-elevated transition-colors"
title={t.sidebar.expand}
>
<svg className="w-3.5 h-3.5 text-text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
)}
{sidebarCollapsed && sidebarPosition === "right" && (
<button
type="button"
onClick={toggleSidebarCollapsed}
className="absolute top-0 right-0 z-10 h-full w-6 bg-surface border-l border-border-subtle flex items-center justify-center hover:bg-elevated transition-colors"
title={t.sidebar.expand}
>
<svg className="w-3.5 h-3.5 text-text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
)}

{/* Graph area */}
<div className="flex-1 min-w-0 min-h-0 relative">
{viewMode === "knowledge" ? (
Expand All @@ -649,10 +722,12 @@ function DashboardContent({
</div>
</div>

{/* Right sidebar — telescopes at narrower widths */}
<aside className="w-[260px] md:w-[300px] lg:w-[360px] shrink-0 bg-surface border-l border-border-subtle overflow-auto">
{sidebarContent}
</aside>
{/* Sidebar on the right side */}
{sidebarPosition === "right" && !sidebarCollapsed && (
<aside className="w-[260px] md:w-[300px] lg:w-[360px] shrink-0 bg-surface border-l border-border-subtle overflow-auto">
{sidebarContent}
</aside>
)}

{/* Code viewer slide-up overlay (collapsed state) */}
{codeViewerOpen && !codeViewerExpanded && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export default function CodeViewer({
const viewMode = useDashboardStore((s) => s.viewMode);
const codeViewerNodeId = useDashboardStore((s) => s.codeViewerNodeId);
const closeCodeViewer = useDashboardStore((s) => s.closeCodeViewer);
const nodeHistory = useDashboardStore((s) => s.nodeHistory);
const goBackNode = useDashboardStore((s) => s.goBackNode);
const openCodeViewer = useDashboardStore((s) => s.openCodeViewer);
const activeGraph = viewMode === "domain" && domainGraph ? domainGraph : graph;
// Files tab always builds its tree from the structural graph, so a node ID opened from
// there may not exist in the active (domain) graph — fall back to the structural graph.
Expand Down Expand Up @@ -167,6 +170,24 @@ export default function CodeViewer({
)}
</div>
<div className="flex items-center gap-2 shrink-0">
{/* Go back button — navigate to previous node in history */}
{nodeHistory.length > 0 && (
<button
type="button"
onClick={() => {
goBackNode();
const prevId = nodeHistory[nodeHistory.length - 1];
openCodeViewer(prevId);
}}
className="text-text-muted hover:text-accent transition-colors"
title={t.codeViewer.goBack}
aria-label={t.codeViewer.goBack}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
)}
{onExpand && (
<button
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { useTheme, PRESETS } from "../themes/index.ts";
import type { HeadingFont } from "../themes/index.ts";
import { useI18n } from "../contexts/I18nContext";
import { type LocaleKey } from "../locales/index.ts";

const LANGUAGE_OPTIONS: { key: LocaleKey; label: string }[] = [
{ key: "en", label: "English" },
{ key: "zh", label: "简体中文" },
{ key: "zh-TW", label: "繁體中文" },
{ key: "ja", label: "日本語" },
{ key: "ko", label: "한국어" },
{ key: "ru", label: "Русский" },
];

export function ThemePicker() {
const { config, preset, setPreset, setAccent, setHeadingFont } = useTheme();
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const { t } = useI18n();
const { t, localeKey, setLanguage } = useI18n();

// Close on outside click
useEffect(() => {
Expand Down Expand Up @@ -173,6 +183,28 @@ export function ThemePicker() {
))}
</div>
</div>

{/* Language */}
<div>
<div className="text-[10px] font-semibold text-text-muted uppercase tracking-wider mb-2">
{t.common.language}
</div>
<div className="flex gap-1 flex-wrap">
{LANGUAGE_OPTIONS.map((opt) => (
<button
key={opt.key}
onClick={() => setLanguage(opt.key)}
className={`px-2 py-1 rounded text-xs transition-colors ${
localeKey === opt.key
? "bg-accent/15 text-accent"
: "text-text-secondary hover:text-text-primary hover:bg-elevated"
}`}
>
{opt.label}
</button>
))}
</div>
</div>
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createContext, useContext, useMemo, type ReactNode } from "react";
import { createContext, useCallback, useContext, useMemo, type ReactNode } from "react";
import { getLocale, resolveLocaleKey, type Locale, type LocaleKey } from "../locales";

interface I18nContextValue {
locale: Locale;
localeKey: LocaleKey;
t: Locale;
setLanguage: (lang: string) => void;
}

const I18nContext = createContext<I18nContextValue | null>(null);
Expand All @@ -19,21 +20,31 @@ export function useI18n(): I18nContextValue {

export function I18nProvider({
language,
onLanguageChange,
children,
}: {
language?: string;
onLanguageChange?: (lang: string) => void;
children: ReactNode;
}) {
const localeKey = useMemo(() => resolveLocaleKey(language), [language]);
const locale = useMemo(() => getLocale(localeKey), [localeKey]);

const setLanguage = useCallback(
(lang: string) => {
onLanguageChange?.(lang);
},
[onLanguageChange]
);

const value = useMemo(
() => ({
locale,
localeKey,
t: locale,
setLanguage,
}),
[locale, localeKey]
[locale, localeKey, setLanguage]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const en = {
pressKeyboard: "Press ? for keyboard shortcuts",
path: "Path",
theme: "Theme",
language: "Language",
},
projectOverview: {
nodes: "Nodes",
Expand Down Expand Up @@ -81,6 +82,10 @@ export const en = {
sidebar: {
info: "Info",
files: "Files",
moveLeft: "Move sidebar left",
moveRight: "Move sidebar right",
collapse: "Collapse sidebar",
expand: "Expand sidebar",
},
mobile: {
graph: "Graph",
Expand Down Expand Up @@ -179,6 +184,7 @@ export const en = {
closeExpanded: "Close expanded code viewer",
closeViewer: "Close code viewer",
sourceUnavailable: "Source unavailable",
goBack: "Go back",
},
customNode: {
tested: "Tested",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const ja = {
pressKeyboard: "? を押してキーボードショートカットを表示",
path: "パス",
theme: "テーマ",
language: "言語",
},
projectOverview: {
nodes: "ノード",
Expand Down Expand Up @@ -81,6 +82,10 @@ export const ja = {
sidebar: {
info: "情報",
files: "ファイル",
moveLeft: "サイドバーを左に移動",
moveRight: "サイドバーを右に移動",
collapse: "サイドバーを折りたたむ",
expand: "サイドバーを展開",
},
mobile: {
graph: "グラフ",
Expand Down Expand Up @@ -179,6 +184,7 @@ export const ja = {
closeExpanded: "展開したコードビューアを閉じる",
closeViewer: "コードビューアを閉じる",
sourceUnavailable: "ソースが利用できません",
goBack: "戻る",
},
customNode: {
tested: "テスト済み",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const ko = {
pressKeyboard: "? 키를 눌러 키보드 단축키 보기",
path: "경로",
theme: "테마",
language: "언어",
},
projectOverview: {
nodes: "노드",
Expand Down Expand Up @@ -81,6 +82,10 @@ export const ko = {
sidebar: {
info: "정보",
files: "파일",
moveLeft: "사이드바 왼쪽으로 이동",
moveRight: "사이드바 오른쪽으로 이동",
collapse: "사이드바 축소",
expand: "사이드바 확장",
},
mobile: {
graph: "그래프",
Expand Down Expand Up @@ -179,6 +184,7 @@ export const ko = {
closeExpanded: "확장된 코드 뷰어 닫기",
closeViewer: "코드 뷰어 닫기",
sourceUnavailable: "소스 사용 불가",
goBack: "돌아가기",
},
customNode: {
tested: "테스트됨",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const ru = {
pressKeyboard: "Нажмите ? для горячих клавиш",
path: "Путь",
theme: "Тема",
language: "Язык",
},
projectOverview: {
nodes: "Узлы",
Expand Down Expand Up @@ -81,6 +82,10 @@ export const ru = {
sidebar: {
info: "Информация",
files: "Файлы",
moveLeft: "Переместить влево",
moveRight: "Переместить вправо",
collapse: "Свернуть",
expand: "Развернуть",
},
mobile: {
graph: "Граф",
Expand Down Expand Up @@ -179,6 +184,7 @@ export const ru = {
closeExpanded: "Закрыть расширенный просмотрщик кода",
closeViewer: "Закрыть просмотрщик кода",
sourceUnavailable: "Исходный код недоступен",
goBack: "Назад",
},
customNode: {
tested: "Покрыт тестами",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const zhTW = {
pressKeyboard: "按 ? 查看鍵盤快捷鍵",
path: "路徑",
theme: "主題",
language: "語言",
},
projectOverview: {
nodes: "節點",
Expand Down Expand Up @@ -81,6 +82,10 @@ export const zhTW = {
sidebar: {
info: "資訊",
files: "檔案",
moveLeft: "側邊欄移至左側",
moveRight: "側邊欄移至右側",
collapse: "收起側邊欄",
expand: "展開側邊欄",
},
mobile: {
graph: "圖谱",
Expand Down Expand Up @@ -179,6 +184,7 @@ export const zhTW = {
closeExpanded: "關閉展開的程式碼檢視器",
closeViewer: "關閉程式碼檢視器",
sourceUnavailable: "原始碼不可用",
goBack: "返回",
},
customNode: {
tested: "已測試",
Expand Down
Loading