diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts index 8849e43be4d..0bfafba2053 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts @@ -13,12 +13,86 @@ export const createQueryProcedures = () => { return router({ get: publicProcedure .input(z.object({ id: z.string() })) - .query(({ input }) => { + .query(async ({ input }) => { const workspace = getWorkspace(input.id); if (!workspace) { throw new Error(`Workspace ${input.id} not found`); } - return workspace; + + const project = localDb + .select() + .from(projects) + .where(eq(projects.id, workspace.projectId)) + .get(); + const worktree = workspace.worktreeId + ? localDb + .select() + .from(worktrees) + .where(eq(worktrees.id, workspace.worktreeId)) + .get() + : null; + + // Detect and persist base branch for existing worktrees that don't have it + // We use undefined to mean "not yet attempted" and null to mean "attempted but not found" + let baseBranch = worktree?.baseBranch; + if (worktree && baseBranch === undefined && project) { + // Only attempt detection if there's a remote origin + const hasRemote = await hasOriginRemote(project.mainRepoPath); + if (hasRemote) { + try { + const defaultBranch = project.defaultBranch || "main"; + const detected = await detectBaseBranch( + worktree.path, + worktree.branch, + defaultBranch, + ); + if (detected) { + baseBranch = detected; + } + // Persist the result (detected branch or null sentinel) + localDb + .update(worktrees) + .set({ baseBranch: detected ?? null }) + .where(eq(worktrees.id, worktree.id)) + .run(); + } catch { + // Detection failed, persist null to avoid retrying + localDb + .update(worktrees) + .set({ baseBranch: null }) + .where(eq(worktrees.id, worktree.id)) + .run(); + } + } else { + // No remote - persist null to avoid retrying + localDb + .update(worktrees) + .set({ baseBranch: null }) + .where(eq(worktrees.id, worktree.id)) + .run(); + } + } + + return { + ...workspace, + type: workspace.type as "worktree" | "branch", + worktreePath: getWorkspacePath(workspace) ?? "", + project: project + ? { + id: project.id, + name: project.name, + mainRepoPath: project.mainRepoPath, + } + : null, + worktree: worktree + ? { + branch: worktree.branch, + baseBranch, + // Normalize to null to ensure consistent "incomplete init" detection in UI + gitStatus: worktree.gitStatus ?? null, + } + : null, + }; }), getAll: publicProcedure.query(() => { diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx index da170ab173d..59ef6fc09c4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx @@ -1,14 +1,69 @@ +import { Avatar } from "@superset/ui/atoms/Avatar"; +import { Button } from "@superset/ui/button"; +import { Skeleton } from "@superset/ui/skeleton"; +import { toast } from "@superset/ui/sonner"; import { createFileRoute } from "@tanstack/react-router"; +import { trpc } from "renderer/lib/trpc"; export const Route = createFileRoute("/_authenticated/settings/account/")({ component: AccountSettingsPage, }); function AccountSettingsPage() { + const { data: user, isLoading } = trpc.user.me.useQuery(); + const signOutMutation = trpc.auth.signOut.useMutation({ + onSuccess: () => toast.success("Signed out"), + }); + + const signOut = () => signOutMutation.mutate(); + return ( -
-

Account Settings

-

Account settings placeholder

+
+
+

Account

+

+ Manage your account settings +

+
+ +
+ {/* Profile Section */} +
+

Profile

+
+ {isLoading ? ( + <> + +
+ + +
+ + ) : user ? ( + <> + +
+

{user.name}

+

{user.email}

+
+ + ) : ( +

Unable to load user info

+ )} +
+
+ + {/* Sign Out Section */} +
+

Sign Out

+

+ Sign out of your Superset account on this device. +

+ +
+
); } diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/ThemeCard.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/ThemeCard/ThemeCard.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/ThemeCard.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/ThemeCard/ThemeCard.tsx diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/ThemeCard/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/ThemeCard/index.ts new file mode 100644 index 00000000000..64e6196ee95 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/ThemeCard/index.ts @@ -0,0 +1 @@ +export { ThemeCard } from "./ThemeCard"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/page.tsx index be99445115b..37933cb71b1 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/page.tsx @@ -1,14 +1,91 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@superset/ui/select"; import { createFileRoute } from "@tanstack/react-router"; +import { + type MarkdownStyle, + useMarkdownStyle, + useSetMarkdownStyle, + useSetTheme, + useThemeId, + useThemeStore, +} from "renderer/stores"; +import { builtInThemes } from "shared/themes"; +import { ThemeCard } from "./components/ThemeCard"; export const Route = createFileRoute("/_authenticated/settings/appearance/")({ component: AppearanceSettingsPage, }); function AppearanceSettingsPage() { + const activeThemeId = useThemeId(); + const setTheme = useSetTheme(); + const customThemes = useThemeStore((state) => state.customThemes); + const markdownStyle = useMarkdownStyle(); + const setMarkdownStyle = useSetMarkdownStyle(); + + const allThemes = [...builtInThemes, ...customThemes]; + return ( -
-

Appearance Settings

-

Appearance settings placeholder

+
+
+

Appearance

+

+ Customize how Superset looks on your device +

+
+ +
+ {/* Theme Section */} +
+

Theme

+
+ {allThemes.map((theme) => ( + setTheme(theme.id)} + /> + ))} +
+
+ +
+

Markdown Style

+

+ Rendering style for markdown files when viewing rendered content +

+ +

+ Tufte style uses elegant serif typography inspired by Edward Tufte's + books +

+
+ +
+

Custom Themes

+

+ Custom theme import coming soon. You'll be able to import JSON theme + files to create your own themes. +

+
+
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx index a77d96e3d42..ccba2afabf4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx @@ -1,14 +1,135 @@ +import type { TerminalLinkBehavior } from "@superset/local-db"; +import { Label } from "@superset/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@superset/ui/select"; +import { Switch } from "@superset/ui/switch"; import { createFileRoute } from "@tanstack/react-router"; +import { trpc } from "renderer/lib/trpc"; export const Route = createFileRoute("/_authenticated/settings/behavior/")({ component: BehaviorSettingsPage, }); function BehaviorSettingsPage() { + const utils = trpc.useUtils(); + + // Confirm on quit setting + const { data: confirmOnQuit, isLoading: isConfirmLoading } = + trpc.settings.getConfirmOnQuit.useQuery(); + const setConfirmOnQuit = trpc.settings.setConfirmOnQuit.useMutation({ + onMutate: async ({ enabled }) => { + await utils.settings.getConfirmOnQuit.cancel(); + const previous = utils.settings.getConfirmOnQuit.getData(); + utils.settings.getConfirmOnQuit.setData(undefined, enabled); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getConfirmOnQuit.setData(undefined, context.previous); + } + }, + onSettled: () => { + utils.settings.getConfirmOnQuit.invalidate(); + }, + }); + + const handleConfirmToggle = (enabled: boolean) => { + setConfirmOnQuit.mutate({ enabled }); + }; + + // Terminal link behavior setting + const { data: terminalLinkBehavior, isLoading: isLoadingLinkBehavior } = + trpc.settings.getTerminalLinkBehavior.useQuery(); + + const setTerminalLinkBehavior = + trpc.settings.setTerminalLinkBehavior.useMutation({ + onMutate: async ({ behavior }) => { + await utils.settings.getTerminalLinkBehavior.cancel(); + const previous = utils.settings.getTerminalLinkBehavior.getData(); + utils.settings.getTerminalLinkBehavior.setData(undefined, behavior); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getTerminalLinkBehavior.setData( + undefined, + context.previous, + ); + } + }, + onSettled: () => { + utils.settings.getTerminalLinkBehavior.invalidate(); + }, + }); + + const handleLinkBehaviorChange = (value: string) => { + setTerminalLinkBehavior.mutate({ + behavior: value as TerminalLinkBehavior, + }); + }; + return ( -
-

Behavior Settings

-

Behavior settings placeholder

+
+
+

Behavior

+

+ Configure app behavior and preferences +

+
+ +
+ {/* Confirm on Quit */} +
+
+ +

+ Show a confirmation dialog when quitting the app +

+
+ +
+ +
+
+ +

+ Choose how to open file paths when Cmd+clicking in the terminal +

+
+ +
+
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/SettingsSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/SettingsSidebar.tsx new file mode 100644 index 00000000000..a9908e92d17 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/SettingsSidebar.tsx @@ -0,0 +1,25 @@ +import { Link } from "@tanstack/react-router"; +import { HiArrowLeft } from "react-icons/hi2"; +import { GeneralSettings } from "./components/GeneralSettings"; +import { ProjectsSettings } from "./components/ProjectsSettings"; + +export function SettingsSidebar() { + return ( +
+ + + Back + + +

Settings

+ +
+ + +
+
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/GeneralSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/GeneralSettings/GeneralSettings.tsx similarity index 50% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/GeneralSettings.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/GeneralSettings/GeneralSettings.tsx index 883e4a7a834..ab5b60538a7 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/GeneralSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/GeneralSettings/GeneralSettings.tsx @@ -1,4 +1,5 @@ import { cn } from "@superset/ui/utils"; +import { Link, useMatchRoute } from "@tanstack/react-router"; import { HiOutlineAdjustmentsHorizontal, HiOutlineBell, @@ -8,81 +9,86 @@ import { HiOutlineUser, HiOutlineUserGroup, } from "react-icons/hi2"; -import type { SettingsSection } from "renderer/stores"; -interface GeneralSettingsProps { - activeSection: SettingsSection; - onSectionChange: (section: SettingsSection) => void; -} +type SettingsRoute = + | "/settings/account" + | "/settings/team" + | "/settings/appearance" + | "/settings/ringtones" + | "/settings/keyboard" + | "/settings/presets" + | "/settings/behavior"; const GENERAL_SECTIONS: { - id: SettingsSection; + id: SettingsRoute; label: string; icon: React.ReactNode; }[] = [ { - id: "account", + id: "/settings/account", label: "Account", icon: , }, { - id: "team", + id: "/settings/team", label: "Team", icon: , }, { - id: "appearance", + id: "/settings/appearance", label: "Appearance", icon: , }, { - id: "ringtones", + id: "/settings/ringtones", label: "Ringtones", icon: , }, { - id: "keyboard", + id: "/settings/keyboard", label: "Keyboard Shortcuts", icon: , }, { - id: "presets", + id: "/settings/presets", label: "Presets", icon: , }, { - id: "behavior", + id: "/settings/behavior", label: "Behavior", icon: , }, ]; -export function GeneralSettings({ - activeSection, - onSectionChange, -}: GeneralSettingsProps) { +export function GeneralSettings() { + const matchRoute = useMatchRoute(); + return (

General

); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/GeneralSettings/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/GeneralSettings/index.ts new file mode 100644 index 00000000000..e4106da087c --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/GeneralSettings/index.ts @@ -0,0 +1 @@ +export { GeneralSettings } from "./GeneralSettings"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/ProjectsSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/ProjectsSettings.tsx new file mode 100644 index 00000000000..3ee7cffa8dc --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/ProjectsSettings.tsx @@ -0,0 +1,124 @@ +import { cn } from "@superset/ui/utils"; +import { Link, useMatchRoute } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; +import { HiChevronDown, HiChevronRight } from "react-icons/hi2"; +import { trpc } from "renderer/lib/trpc"; + +export function ProjectsSettings() { + const { data: groups = [] } = trpc.workspaces.getAllGrouped.useQuery(); + const matchRoute = useMatchRoute(); + const [expandedProjects, setExpandedProjects] = useState>( + new Set(), + ); + + // Expand all projects by default when groups are loaded + useEffect(() => { + if (groups.length > 0) { + setExpandedProjects(new Set(groups.map((g) => g.project.id))); + } + }, [groups]); + + const toggleProject = (projectId: string) => { + setExpandedProjects((prev) => { + const next = new Set(prev); + if (next.has(projectId)) { + next.delete(projectId); + } else { + next.add(projectId); + } + return next; + }); + }; + + if (groups.length === 0) { + return null; + } + + return ( +
+

+ Projects +

+ +
+ ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/index.ts new file mode 100644 index 00000000000..ec04db77c75 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/index.ts @@ -0,0 +1 @@ +export { ProjectsSettings } from "./ProjectsSettings"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/index.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/index.ts rename to apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/index.ts diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx index a96efd9cbf2..7ae403a81c9 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx @@ -1,14 +1,416 @@ +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@superset/ui/alert-dialog"; +import { Button } from "@superset/ui/button"; +import { Input } from "@superset/ui/input"; +import { Kbd, KbdGroup } from "@superset/ui/kbd"; +import { toast } from "@superset/ui/sonner"; import { createFileRoute } from "@tanstack/react-router"; +import { useEffect, useMemo, useState } from "react"; +import { HiMagnifyingGlass } from "react-icons/hi2"; +import { trpc } from "renderer/lib/trpc"; +import { + captureHotkeyFromEvent, + getHotkeyConflict, + useHotkeyDisplay, + useHotkeysByCategory, + useHotkeysStore, +} from "renderer/stores/hotkeys"; +import { + formatHotkeyText, + HOTKEYS, + type HotkeyCategory, + type HotkeyId, + type HotkeysState, + isOsReservedHotkey, + isTerminalReservedHotkey, +} from "shared/hotkeys"; + +const CATEGORY_ORDER: HotkeyCategory[] = [ + "Workspace", + "Terminal", + "Layout", + "Window", + "Help", +]; + +function HotkeyRow({ + id, + label, + description, + isRecording, + onStartRecording, + onReset, +}: { + id: HotkeyId; + label: string; + description?: string; + isRecording: boolean; + onStartRecording: () => void; + onReset: () => void; +}) { + const display = useHotkeyDisplay(id); + + return ( +
+
+ {label} + {description && ( + {description} + )} +
+
+ + +
+
+ ); +} export const Route = createFileRoute("/_authenticated/settings/keyboard/")({ - component: KeyboardSettingsPage, + component: KeyboardShortcutsPage, }); -function KeyboardSettingsPage() { +function KeyboardShortcutsPage() { + const [searchQuery, setSearchQuery] = useState(""); + const [recordingId, setRecordingId] = useState(null); + const [pendingConflict, setPendingConflict] = useState<{ + id: HotkeyId; + keys: string; + conflictId: HotkeyId; + } | null>(null); + const [pendingImport, setPendingImport] = useState<{ + path: string; + state: HotkeysState; + summary: { assigned: number; disabled: number }; + } | null>(null); + + const platform = useHotkeysStore((state) => state.platform); + const setHotkey = useHotkeysStore((state) => state.setHotkey); + const setHotkeysBatch = useHotkeysStore((state) => state.setHotkeysBatch); + const resetHotkey = useHotkeysStore((state) => state.resetHotkey); + const resetAllHotkeys = useHotkeysStore((state) => state.resetAllHotkeys); + const replaceHotkeysState = useHotkeysStore( + (state) => state.replaceHotkeysState, + ); + const hotkeysByCategory = useHotkeysByCategory(); + + const exportMutation = trpc.hotkeys.export.useMutation(); + const importMutation = trpc.hotkeys.import.useMutation(); + + const showHotkeysDisplay = useHotkeyDisplay("SHOW_HOTKEYS"); + + const allHotkeys = useMemo( + () => + CATEGORY_ORDER.flatMap((category) => hotkeysByCategory[category] ?? []), + [hotkeysByCategory], + ); + + const filteredHotkeys = useMemo(() => { + if (!searchQuery) return allHotkeys; + const lower = searchQuery.toLowerCase(); + return allHotkeys.filter((hotkey) => + hotkey.label.toLowerCase().includes(lower), + ); + }, [allHotkeys, searchQuery]); + + useEffect(() => { + if (!recordingId) return; + + const handleKeyDown = (event: KeyboardEvent) => { + event.preventDefault(); + event.stopPropagation(); + + if (event.key === "Escape") { + setRecordingId(null); + return; + } + + if (event.key === "Backspace" || event.key === "Delete") { + setHotkey(recordingId, null); + setRecordingId(null); + return; + } + + const captured = captureHotkeyFromEvent(event, platform); + if (!captured) return; + + if (isTerminalReservedHotkey(captured)) { + toast.error("That shortcut is reserved by the terminal."); + setRecordingId(null); + return; + } + + const conflictId = getHotkeyConflict(captured, recordingId); + if (conflictId) { + setPendingConflict({ id: recordingId, keys: captured, conflictId }); + setRecordingId(null); + return; + } + + if (isOsReservedHotkey(captured, platform)) { + toast.warning("This shortcut may be reserved by your OS."); + } + + setHotkey(recordingId, captured); + setRecordingId(null); + }; + + window.addEventListener("keydown", handleKeyDown, { capture: true }); + return () => { + window.removeEventListener("keydown", handleKeyDown, { capture: true }); + }; + }, [recordingId, platform, setHotkey]); + + const handleStartRecording = (id: HotkeyId) => { + setRecordingId((current) => (current === id ? null : id)); + }; + + const handleExport = async () => { + try { + const result = await exportMutation.mutateAsync(); + if ("canceled" in result && result.canceled) return; + if ("error" in result) { + toast.error("Failed to export shortcuts", { + description: result.error, + }); + return; + } + toast.success("Keyboard shortcuts exported", { + description: result.path, + }); + } catch (error) { + toast.error("Failed to export shortcuts", { + description: error instanceof Error ? error.message : undefined, + }); + } + }; + + const handleImport = async () => { + try { + const result = await importMutation.mutateAsync(); + if ("canceled" in result && result.canceled) return; + if ("error" in result) { + toast.error("Failed to import shortcuts", { + description: result.error, + }); + return; + } + setPendingImport({ + path: result.path, + state: result.state, + summary: result.summary, + }); + } catch (error) { + toast.error("Failed to import shortcuts", { + description: error instanceof Error ? error.message : undefined, + }); + } + }; + + const handleConfirmImport = () => { + if (!pendingImport) return; + replaceHotkeysState(pendingImport.state); + toast.success("Keyboard shortcuts imported"); + setPendingImport(null); + }; + + const handleConflictReassign = () => { + if (!pendingConflict) return; + setHotkeysBatch({ + [pendingConflict.conflictId]: null, + [pendingConflict.id]: pendingConflict.keys, + }); + if (isOsReservedHotkey(pendingConflict.keys, platform)) { + toast.warning("This shortcut may be reserved by your OS."); + } + setPendingConflict(null); + }; + return ( -
-

Keyboard Settings

-

Keyboard settings placeholder

+
+ {/* Header */} +
+
+

Keyboard Shortcuts

+

+ Customize keyboard shortcuts for your workflow. Press{" "} + + {showHotkeysDisplay.map((key) => ( + {key} + ))} + {" "} + to open this page anytime. +

+
+
+ + + +
+
+ + {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="pl-9 bg-accent/30 border-transparent focus:border-accent" + /> +
+ + {/* Table */} +
+
+ + Command + + + Shortcut + +
+ +
+ {filteredHotkeys.length > 0 ? ( + filteredHotkeys.map((hotkey) => ( + handleStartRecording(hotkey.id)} + onReset={() => resetHotkey(hotkey.id)} + /> + )) + ) : ( +
+ No shortcuts found matching "{searchQuery}" +
+ )} +
+
+ + {/* Conflict dialog */} + setPendingConflict(null)} + > + + + + Shortcut already in use + + +
+ + {pendingConflict + ? `${formatHotkeyText( + pendingConflict.keys, + platform, + )} is already assigned to “${ + HOTKEYS[pendingConflict.conflictId].label + }”.` + : ""} + + Would you like to reassign it? +
+
+
+ + + + +
+
+ + {/* Import dialog */} + setPendingImport(null)} + > + + + + Import keyboard shortcuts? + + +
+ + This will replace your shortcuts on all platforms. + + {pendingImport && ( + + {pendingImport.summary.assigned} assigned,{" "} + {pendingImport.summary.disabled} disabled on {platform}. + + )} +
+
+
+ + + + +
+
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx new file mode 100644 index 00000000000..d5cfec26066 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx @@ -0,0 +1,32 @@ +import { createFileRoute, Outlet } from "@tanstack/react-router"; +import { trpc } from "renderer/lib/trpc"; +import { SettingsSidebar } from "./components/SettingsSidebar"; + +export const Route = createFileRoute("/_authenticated/settings")({ + component: SettingsLayout, +}); + +function SettingsLayout() { + const { data: platform } = trpc.window.getPlatform.useQuery(); + const isMac = platform === undefined || platform === "darwin"; + + return ( +
+ {/* Top bar with Mac spacing - invisible but reserves space */} +
+ + {/* Main content */} +
+ +
+ +
+
+
+ ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/page.tsx index ebe358f19b5..907e8d184e1 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/page.tsx @@ -5,6 +5,5 @@ export const Route = createFileRoute("/_authenticated/settings/")({ }); function SettingsPage() { - // Redirect to account settings by default return ; } diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/CommandsEditor/CommandsEditor.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/CommandsEditor/CommandsEditor.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/CommandsEditor/CommandsEditor.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/CommandsEditor/CommandsEditor.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/CommandsEditor/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/CommandsEditor/index.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/CommandsEditor/index.ts rename to apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/CommandsEditor/index.ts diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/PresetRow.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/PresetRow/PresetRow.tsx similarity index 99% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/PresetRow.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/PresetRow/PresetRow.tsx index aecd60bce08..14b466715ec 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/PresetRow.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/PresetRow/PresetRow.tsx @@ -3,13 +3,13 @@ import { Input } from "@superset/ui/input"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { HiOutlineStar, HiStar } from "react-icons/hi2"; import { LuTrash } from "react-icons/lu"; -import { CommandsEditor } from "../CommandsEditor"; import { PRESET_COLUMNS, type PresetColumnConfig, type PresetColumnKey, type TerminalPreset, -} from "../types"; +} from "../../types"; +import { CommandsEditor } from "../CommandsEditor"; interface PresetCellProps { column: PresetColumnConfig; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/PresetRow/index.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/index.ts rename to apps/desktop/src/renderer/routes/_authenticated/settings/presets/components/PresetRow/index.ts diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/presets/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/page.tsx index d3f2b4c77a5..c7352f5f3f6 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/presets/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/page.tsx @@ -1,14 +1,284 @@ +import { Button } from "@superset/ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { createFileRoute } from "@tanstack/react-router"; +import { useEffect, useMemo, useState } from "react"; +import { HiOutlineCheck, HiOutlinePlus } from "react-icons/hi2"; +import { + getPresetIcon, + useIsDarkTheme, +} from "renderer/assets/app-icons/preset-icons"; +import { usePresets } from "renderer/react-query/presets"; +import { PresetRow } from "./components/PresetRow"; +import { + PRESET_COLUMNS, + type PresetColumnKey, + type TerminalPreset, +} from "./types"; + +interface PresetTemplate { + name: string; + preset: { + name: string; + description: string; + cwd: string; + commands: string[]; + }; +} + +const PRESET_TEMPLATES: PresetTemplate[] = [ + { + name: "codex", + preset: { + name: "codex", + description: "Danger mode: All permissions auto-approved", + cwd: "", + commands: [ + 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true', + ], + }, + }, + { + name: "claude", + preset: { + name: "claude", + description: "Danger mode: All permissions auto-approved", + cwd: "", + commands: ["claude --dangerously-skip-permissions"], + }, + }, + { + name: "gemini", + preset: { + name: "gemini", + description: "Danger mode: All permissions auto-approved", + cwd: "", + commands: ["gemini --yolo"], + }, + }, + { + name: "cursor-agent", + preset: { + name: "cursor-agent", + description: "Cursor AI agent for terminal-based coding assistance", + cwd: "", + commands: ["cursor-agent"], + }, + }, + { + name: "opencode", + preset: { + name: "opencode", + description: "OpenCode: Open source AI coding agent", + cwd: "", + commands: ["opencode"], + }, + }, +]; export const Route = createFileRoute("/_authenticated/settings/presets/")({ component: PresetsSettingsPage, }); function PresetsSettingsPage() { + const { + presets: serverPresets, + isLoading, + createPreset, + updatePreset, + deletePreset, + setDefaultPreset, + } = usePresets(); + const [localPresets, setLocalPresets] = + useState(serverPresets); + const isDark = useIsDarkTheme(); + + useEffect(() => { + setLocalPresets(serverPresets); + }, [serverPresets]); + + const existingPresetNames = useMemo( + () => new Set(serverPresets.map((p) => p.name)), + [serverPresets], + ); + + const isTemplateAdded = (template: PresetTemplate) => + existingPresetNames.has(template.preset.name); + + const handleCellChange = ( + rowIndex: number, + column: PresetColumnKey, + value: string, + ) => { + setLocalPresets((prev) => + prev.map((p, i) => (i === rowIndex ? { ...p, [column]: value } : p)), + ); + }; + + const handleCellBlur = (rowIndex: number, column: PresetColumnKey) => { + const preset = localPresets[rowIndex]; + const serverPreset = serverPresets[rowIndex]; + if (!preset || !serverPreset) return; + if (preset[column] === serverPreset[column]) return; + + updatePreset.mutate({ + id: preset.id, + patch: { [column]: preset[column] }, + }); + }; + + const handleCommandsChange = (rowIndex: number, commands: string[]) => { + setLocalPresets((prev) => + prev.map((p, i) => (i === rowIndex ? { ...p, commands } : p)), + ); + }; + + const handleCommandsBlur = (rowIndex: number) => { + const preset = localPresets[rowIndex]; + const serverPreset = serverPresets[rowIndex]; + if (!preset || !serverPreset) return; + if ( + JSON.stringify(preset.commands) === JSON.stringify(serverPreset.commands) + ) + return; + + updatePreset.mutate({ + id: preset.id, + patch: { commands: preset.commands }, + }); + }; + + const handleAddRow = () => { + createPreset.mutate({ + name: "", + cwd: "", + commands: [""], + }); + }; + + const handleAddTemplate = (template: PresetTemplate) => { + if (isTemplateAdded(template)) return; + createPreset.mutate(template.preset); + }; + + const handleDeleteRow = (rowIndex: number) => { + const preset = localPresets[rowIndex]; + if (!preset) return; + + deletePreset.mutate({ id: preset.id }); + }; + + const handleSetDefault = (presetId: string | null) => { + setDefaultPreset.mutate({ id: presetId }); + }; + + if (isLoading) { + return ( +
+
+
+
+
+
+ ); + } + return ( -
-

Presets Settings

-

Presets settings placeholder

+
+
+
+

Terminal Presets

+ +
+

+ Presets let you quickly launch terminals with pre-configured commands. + Create a preset below, then use it from the "New Terminal" dropdown in + any workspace. +

+ +
+ + Quick add: + + {PRESET_TEMPLATES.map((template) => { + const alreadyAdded = isTemplateAdded(template); + const presetIcon = getPresetIcon(template.name, isDark); + return ( + + + + + + {alreadyAdded ? "Already added" : template.preset.description} + + + ); + })} +
+
+ +
+
+ {PRESET_COLUMNS.map((column) => ( +
+ {column.label} +
+ ))} +
+ Actions +
+
+ +
+ {localPresets.length > 0 ? ( + localPresets.map((preset, index) => ( + + )) + ) : ( +
+ No presets yet. Click "Add Preset" to create your first preset. +
+ )} +
+
); } diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/types.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/types.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/types.ts rename to apps/desktop/src/renderer/routes/_authenticated/settings/presets/types.ts diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/ProjectSettings/ProjectSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx similarity index 81% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/ProjectSettings/ProjectSettings.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx index 4023bd30876..eb4dcba1e8a 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/ProjectSettings/ProjectSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx @@ -1,14 +1,24 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute( + "/_authenticated/settings/project/$projectId/", +)({ + component: ProjectSettingsPage, +}); + import { HiOutlineCog6Tooth, HiOutlineFolder } from "react-icons/hi2"; import { ConfigFilePreview } from "renderer/components/ConfigFilePreview"; import { trpc } from "renderer/lib/trpc"; -export function ProjectSettings() { - const { data: activeWorkspace, isLoading } = - trpc.workspaces.getActive.useQuery(); +function ProjectSettingsPage() { + const { projectId } = Route.useParams(); + const { data: project, isLoading } = trpc.projects.get.useQuery({ + id: projectId, + }); const { data: configFilePath } = trpc.config.getConfigFilePath.useQuery( - { projectId: activeWorkspace?.projectId ?? "" }, - { enabled: !!activeWorkspace?.projectId }, + { projectId }, + { enabled: !!projectId }, ); if (isLoading) { @@ -22,21 +32,19 @@ export function ProjectSettings() { ); } - if (!activeWorkspace?.project) { + if (!project) { return (

Project

- No active project selected + Project not found

); } - const { project } = activeWorkspace; - return (
diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/RingtonesSettings/RingtonesSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/page.tsx similarity index 97% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/RingtonesSettings/RingtonesSettings.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/page.tsx index 85d4d7fee61..df4941687e5 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/RingtonesSettings/RingtonesSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/page.tsx @@ -1,4 +1,5 @@ import { cn } from "@superset/ui/utils"; +import { createFileRoute } from "@tanstack/react-router"; import { useCallback, useEffect, useRef, useState } from "react"; import { HiBellSlash, HiCheck, HiPlay, HiStop } from "react-icons/hi2"; import { trpcClient } from "renderer/lib/trpc-client"; @@ -149,7 +150,11 @@ function RingtoneCard({ ); } -export function RingtonesSettings() { +export const Route = createFileRoute("/_authenticated/settings/ringtones/")({ + component: RingtonesSettingsPage, +}); + +function RingtonesSettingsPage() { const selectedRingtoneId = useSelectedRingtoneId(); const setRingtone = useSetRingtone(); const [playingId, setPlayingId] = useState(null); diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/InviteMemberButton/InviteMemberButton.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/InviteMemberButton/InviteMemberButton.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/InviteMemberButton/InviteMemberButton.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/team/components/InviteMemberButton/InviteMemberButton.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/InviteMemberButton/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/InviteMemberButton/index.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/InviteMemberButton/index.ts rename to apps/desktop/src/renderer/routes/_authenticated/settings/team/components/InviteMemberButton/index.ts diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberActions/MemberActions.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/MemberActions.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberActions/MemberActions.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/MemberActions.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberActions/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/index.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberActions/index.ts rename to apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/index.ts diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/TeamSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/team/page.tsx similarity index 96% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/TeamSettings.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/team/page.tsx index fee5bc4dd34..8c2fc5ec966 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/TeamSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/team/page.tsx @@ -11,12 +11,17 @@ import { } from "@superset/ui/table"; import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; +import { createFileRoute } from "@tanstack/react-router"; import { useAuth } from "renderer/providers/AuthProvider"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import { InviteMemberButton } from "./components/InviteMemberButton"; import { MemberActions } from "./components/MemberActions"; -export function TeamSettings() { +export const Route = createFileRoute("/_authenticated/settings/team/")({ + component: TeamSettingsPage, +}); + +function TeamSettingsPage() { const { session } = useAuth(); const collections = useCollections(); diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/WorkspaceSettings/WorkspaceSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx similarity index 79% rename from apps/desktop/src/renderer/screens/main/components/SettingsView/WorkspaceSettings/WorkspaceSettings.tsx rename to apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx index 74285a1a968..b09edbacbbf 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/WorkspaceSettings/WorkspaceSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx @@ -1,17 +1,24 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute( + "/_authenticated/settings/workspace/$workspaceId/", +)({ + component: WorkspaceSettingsPage, +}); + import { Input } from "@superset/ui/input"; import { HiOutlineFolder, HiOutlinePencilSquare } from "react-icons/hi2"; import { LuGitBranch } from "react-icons/lu"; import { trpc } from "renderer/lib/trpc"; import { useWorkspaceRename } from "renderer/screens/main/hooks/useWorkspaceRename"; -export function WorkspaceSettings() { - const { data: activeWorkspace, isLoading } = - trpc.workspaces.getActive.useQuery(); +function WorkspaceSettingsPage() { + const { workspaceId } = Route.useParams(); + const { data: workspace, isLoading } = trpc.workspaces.get.useQuery({ + id: workspaceId, + }); - const rename = useWorkspaceRename( - activeWorkspace?.id ?? "", - activeWorkspace?.name ?? "", - ); + const rename = useWorkspaceRename(workspace?.id ?? "", workspace?.name ?? ""); if (isLoading) { return ( @@ -24,13 +31,13 @@ export function WorkspaceSettings() { ); } - if (!activeWorkspace) { + if (!workspace) { return (

Workspace

- No active workspace selected + Workspace not found

@@ -75,21 +82,21 @@ export function WorkspaceSettings() { className="group flex items-center gap-2 cursor-pointer hover:text-foreground/80 transition-colors text-left" onClick={rename.startRename} > - {activeWorkspace.name} + {workspace.name} )}
- {activeWorkspace.worktree && ( + {workspace.worktree && (

Branch

-

{activeWorkspace.worktree.branch}

- {activeWorkspace.worktree.gitStatus?.needsRebase && ( +

{workspace.worktree.branch}

+ {workspace.worktree.gitStatus?.needsRebase && ( Needs Rebase @@ -104,7 +111,7 @@ export function WorkspaceSettings() { Path

- {activeWorkspace.worktreePath} + {workspace.worktreePath}

diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/page.tsx deleted file mode 100644 index 4ecebbd064b..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/_authenticated/settings/workspace/")({ - component: WorkspaceSettingsPage, -}); - -function WorkspaceSettingsPage() { - return ( -
-

Workspace Settings

-

Workspace settings placeholder

-
- ); -} diff --git a/apps/desktop/src/renderer/routes/sign-in/page.tsx b/apps/desktop/src/renderer/routes/sign-in/page.tsx index 5140a0acfa6..2cbddbcc7bf 100644 --- a/apps/desktop/src/renderer/routes/sign-in/page.tsx +++ b/apps/desktop/src/renderer/routes/sign-in/page.tsx @@ -21,7 +21,6 @@ function SignInPage() { posthog.capture("desktop_opened"); }, []); - // Redirect to workspace if already authenticated const isSignedIn = !!token && !!session?.user; if (isSignedIn) { return ; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsButton/SettingsButton.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsButton/SettingsButton.tsx index c67578c3a0b..00be6e09c3d 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsButton/SettingsButton.tsx +++ b/apps/desktop/src/renderer/screens/main/components/SettingsButton/SettingsButton.tsx @@ -1,11 +1,11 @@ import { Button } from "@superset/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { useNavigate } from "@tanstack/react-router"; import { CiSettings } from "react-icons/ci"; import { HotkeyTooltipContent } from "renderer/components/HotkeyTooltipContent"; -import { useOpenSettings } from "renderer/stores"; export function SettingsButton() { - const openSettings = useOpenSettings(); + const navigate = useNavigate(); return ( @@ -13,7 +13,7 @@ export function SettingsButton() { -
-
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/AccountSettings/index.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/AccountSettings/index.ts deleted file mode 100644 index f6b6c7c72e6..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/AccountSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AccountSettings } from "./AccountSettings"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/AppearanceSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/AppearanceSettings.tsx deleted file mode 100644 index ce8e19aff69..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/AppearanceSettings.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@superset/ui/select"; -import { - type MarkdownStyle, - useMarkdownStyle, - useSetMarkdownStyle, - useSetTheme, - useThemeId, - useThemeStore, -} from "renderer/stores"; -import { builtInThemes } from "shared/themes"; -import { ThemeCard } from "./ThemeCard"; - -export function AppearanceSettings() { - const activeThemeId = useThemeId(); - const setTheme = useSetTheme(); - const customThemes = useThemeStore((state) => state.customThemes); - const markdownStyle = useMarkdownStyle(); - const setMarkdownStyle = useSetMarkdownStyle(); - - const allThemes = [...builtInThemes, ...customThemes]; - - return ( -
-
-

Appearance

-

- Customize how Superset looks on your device -

-
- -
- {/* Theme Section */} -
-

Theme

-
- {allThemes.map((theme) => ( - setTheme(theme.id)} - /> - ))} -
-
- -
-

Markdown Style

-

- Rendering style for markdown files when viewing rendered content -

- -

- Tufte style uses elegant serif typography inspired by Edward Tufte's - books -

-
- -
-

Custom Themes

-

- Custom theme import coming soon. You'll be able to import JSON theme - files to create your own themes. -

-
-
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/BehaviorSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/BehaviorSettings.tsx deleted file mode 100644 index 7bbac096c0f..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/BehaviorSettings.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import type { TerminalLinkBehavior } from "@superset/local-db"; -import { Label } from "@superset/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@superset/ui/select"; -import { Switch } from "@superset/ui/switch"; -import { trpc } from "renderer/lib/trpc"; - -export function BehaviorSettings() { - const utils = trpc.useUtils(); - - // Confirm on quit setting - const { data: confirmOnQuit, isLoading: isConfirmLoading } = - trpc.settings.getConfirmOnQuit.useQuery(); - const setConfirmOnQuit = trpc.settings.setConfirmOnQuit.useMutation({ - onMutate: async ({ enabled }) => { - await utils.settings.getConfirmOnQuit.cancel(); - const previous = utils.settings.getConfirmOnQuit.getData(); - utils.settings.getConfirmOnQuit.setData(undefined, enabled); - return { previous }; - }, - onError: (_err, _vars, context) => { - if (context?.previous !== undefined) { - utils.settings.getConfirmOnQuit.setData(undefined, context.previous); - } - }, - onSettled: () => { - utils.settings.getConfirmOnQuit.invalidate(); - }, - }); - - const handleConfirmToggle = (enabled: boolean) => { - setConfirmOnQuit.mutate({ enabled }); - }; - - // Terminal link behavior setting - const { data: terminalLinkBehavior, isLoading: isLoadingLinkBehavior } = - trpc.settings.getTerminalLinkBehavior.useQuery(); - - const setTerminalLinkBehavior = - trpc.settings.setTerminalLinkBehavior.useMutation({ - onMutate: async ({ behavior }) => { - await utils.settings.getTerminalLinkBehavior.cancel(); - const previous = utils.settings.getTerminalLinkBehavior.getData(); - utils.settings.getTerminalLinkBehavior.setData(undefined, behavior); - return { previous }; - }, - onError: (_err, _vars, context) => { - if (context?.previous !== undefined) { - utils.settings.getTerminalLinkBehavior.setData( - undefined, - context.previous, - ); - } - }, - onSettled: () => { - utils.settings.getTerminalLinkBehavior.invalidate(); - }, - }); - - const handleLinkBehaviorChange = (value: string) => { - setTerminalLinkBehavior.mutate({ - behavior: value as TerminalLinkBehavior, - }); - }; - - return ( -
-
-

Behavior

-

- Configure app behavior and preferences -

-
- -
- {/* Confirm on Quit */} -
-
- -

- Show a confirmation dialog when quitting the app -

-
- -
- -
-
- -

- Choose how to open file paths when Cmd+clicking in the terminal -

-
- -
-
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx deleted file mode 100644 index 4917bbdbd76..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx +++ /dev/null @@ -1,411 +0,0 @@ -import { - AlertDialog, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@superset/ui/alert-dialog"; -import { Button } from "@superset/ui/button"; -import { Input } from "@superset/ui/input"; -import { Kbd, KbdGroup } from "@superset/ui/kbd"; -import { toast } from "@superset/ui/sonner"; -import { useEffect, useMemo, useState } from "react"; -import { HiMagnifyingGlass } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; -import { - captureHotkeyFromEvent, - getHotkeyConflict, - useHotkeyDisplay, - useHotkeysByCategory, - useHotkeysStore, -} from "renderer/stores/hotkeys"; -import { - formatHotkeyText, - HOTKEYS, - type HotkeyCategory, - type HotkeyId, - type HotkeysState, - isOsReservedHotkey, - isTerminalReservedHotkey, -} from "shared/hotkeys"; - -const CATEGORY_ORDER: HotkeyCategory[] = [ - "Workspace", - "Terminal", - "Layout", - "Window", - "Help", -]; - -function HotkeyRow({ - id, - label, - description, - isRecording, - onStartRecording, - onReset, -}: { - id: HotkeyId; - label: string; - description?: string; - isRecording: boolean; - onStartRecording: () => void; - onReset: () => void; -}) { - const display = useHotkeyDisplay(id); - - return ( -
-
- {label} - {description && ( - {description} - )} -
-
- - -
-
- ); -} - -export function KeyboardShortcutsSettings() { - const [searchQuery, setSearchQuery] = useState(""); - const [recordingId, setRecordingId] = useState(null); - const [pendingConflict, setPendingConflict] = useState<{ - id: HotkeyId; - keys: string; - conflictId: HotkeyId; - } | null>(null); - const [pendingImport, setPendingImport] = useState<{ - path: string; - state: HotkeysState; - summary: { assigned: number; disabled: number }; - } | null>(null); - - const platform = useHotkeysStore((state) => state.platform); - const setHotkey = useHotkeysStore((state) => state.setHotkey); - const setHotkeysBatch = useHotkeysStore((state) => state.setHotkeysBatch); - const resetHotkey = useHotkeysStore((state) => state.resetHotkey); - const resetAllHotkeys = useHotkeysStore((state) => state.resetAllHotkeys); - const replaceHotkeysState = useHotkeysStore( - (state) => state.replaceHotkeysState, - ); - const hotkeysByCategory = useHotkeysByCategory(); - - const exportMutation = trpc.hotkeys.export.useMutation(); - const importMutation = trpc.hotkeys.import.useMutation(); - - const showHotkeysDisplay = useHotkeyDisplay("SHOW_HOTKEYS"); - - const allHotkeys = useMemo( - () => - CATEGORY_ORDER.flatMap((category) => hotkeysByCategory[category] ?? []), - [hotkeysByCategory], - ); - - const filteredHotkeys = useMemo(() => { - if (!searchQuery) return allHotkeys; - const lower = searchQuery.toLowerCase(); - return allHotkeys.filter((hotkey) => - hotkey.label.toLowerCase().includes(lower), - ); - }, [allHotkeys, searchQuery]); - - useEffect(() => { - if (!recordingId) return; - - const handleKeyDown = (event: KeyboardEvent) => { - event.preventDefault(); - event.stopPropagation(); - - if (event.key === "Escape") { - setRecordingId(null); - return; - } - - if (event.key === "Backspace" || event.key === "Delete") { - setHotkey(recordingId, null); - setRecordingId(null); - return; - } - - const captured = captureHotkeyFromEvent(event, platform); - if (!captured) return; - - if (isTerminalReservedHotkey(captured)) { - toast.error("That shortcut is reserved by the terminal."); - setRecordingId(null); - return; - } - - const conflictId = getHotkeyConflict(captured, recordingId); - if (conflictId) { - setPendingConflict({ id: recordingId, keys: captured, conflictId }); - setRecordingId(null); - return; - } - - if (isOsReservedHotkey(captured, platform)) { - toast.warning("This shortcut may be reserved by your OS."); - } - - setHotkey(recordingId, captured); - setRecordingId(null); - }; - - window.addEventListener("keydown", handleKeyDown, { capture: true }); - return () => { - window.removeEventListener("keydown", handleKeyDown, { capture: true }); - }; - }, [recordingId, platform, setHotkey]); - - const handleStartRecording = (id: HotkeyId) => { - setRecordingId((current) => (current === id ? null : id)); - }; - - const handleExport = async () => { - try { - const result = await exportMutation.mutateAsync(); - if ("canceled" in result && result.canceled) return; - if ("error" in result) { - toast.error("Failed to export shortcuts", { - description: result.error, - }); - return; - } - toast.success("Keyboard shortcuts exported", { - description: result.path, - }); - } catch (error) { - toast.error("Failed to export shortcuts", { - description: error instanceof Error ? error.message : undefined, - }); - } - }; - - const handleImport = async () => { - try { - const result = await importMutation.mutateAsync(); - if ("canceled" in result && result.canceled) return; - if ("error" in result) { - toast.error("Failed to import shortcuts", { - description: result.error, - }); - return; - } - setPendingImport({ - path: result.path, - state: result.state, - summary: result.summary, - }); - } catch (error) { - toast.error("Failed to import shortcuts", { - description: error instanceof Error ? error.message : undefined, - }); - } - }; - - const handleConfirmImport = () => { - if (!pendingImport) return; - replaceHotkeysState(pendingImport.state); - toast.success("Keyboard shortcuts imported"); - setPendingImport(null); - }; - - const handleConflictReassign = () => { - if (!pendingConflict) return; - setHotkeysBatch({ - [pendingConflict.conflictId]: null, - [pendingConflict.id]: pendingConflict.keys, - }); - if (isOsReservedHotkey(pendingConflict.keys, platform)) { - toast.warning("This shortcut may be reserved by your OS."); - } - setPendingConflict(null); - }; - - return ( -
- {/* Header */} -
-
-

Keyboard Shortcuts

-

- Customize keyboard shortcuts for your workflow. Press{" "} - - {showHotkeysDisplay.map((key) => ( - {key} - ))} - {" "} - to open this page anytime. -

-
-
- - - -
-
- - {/* Search */} -
- - setSearchQuery(e.target.value)} - className="pl-9 bg-accent/30 border-transparent focus:border-accent" - /> -
- - {/* Table */} -
-
- - Command - - - Shortcut - -
- -
- {filteredHotkeys.length > 0 ? ( - filteredHotkeys.map((hotkey) => ( - handleStartRecording(hotkey.id)} - onReset={() => resetHotkey(hotkey.id)} - /> - )) - ) : ( -
- No shortcuts found matching "{searchQuery}" -
- )} -
-
- - {/* Conflict dialog */} - setPendingConflict(null)} - > - - - - Shortcut already in use - - -
- - {pendingConflict - ? `${formatHotkeyText( - pendingConflict.keys, - platform, - )} is already assigned to “${ - HOTKEYS[pendingConflict.conflictId].label - }”.` - : ""} - - Would you like to reassign it? -
-
-
- - - - -
-
- - {/* Import dialog */} - setPendingImport(null)} - > - - - - Import keyboard shortcuts? - - -
- - This will replace your shortcuts on all platforms. - - {pendingImport && ( - - {pendingImport.summary.assigned} assigned,{" "} - {pendingImport.summary.disabled} disabled on {platform}. - - )} -
-
-
- - - - -
-
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetsSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetsSettings.tsx deleted file mode 100644 index debc18b36a6..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetsSettings.tsx +++ /dev/null @@ -1,279 +0,0 @@ -import { Button } from "@superset/ui/button"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { useEffect, useMemo, useState } from "react"; -import { HiOutlineCheck, HiOutlinePlus } from "react-icons/hi2"; -import { - getPresetIcon, - useIsDarkTheme, -} from "renderer/assets/app-icons/preset-icons"; -import { usePresets } from "renderer/react-query/presets"; -import { PresetRow } from "./PresetRow"; -import { - PRESET_COLUMNS, - type PresetColumnKey, - type TerminalPreset, -} from "./types"; - -interface PresetTemplate { - name: string; - preset: { - name: string; - description: string; - cwd: string; - commands: string[]; - }; -} - -const PRESET_TEMPLATES: PresetTemplate[] = [ - { - name: "codex", - preset: { - name: "codex", - description: "Danger mode: All permissions auto-approved", - cwd: "", - commands: [ - 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true', - ], - }, - }, - { - name: "claude", - preset: { - name: "claude", - description: "Danger mode: All permissions auto-approved", - cwd: "", - commands: ["claude --dangerously-skip-permissions"], - }, - }, - { - name: "gemini", - preset: { - name: "gemini", - description: "Danger mode: All permissions auto-approved", - cwd: "", - commands: ["gemini --yolo"], - }, - }, - { - name: "cursor-agent", - preset: { - name: "cursor-agent", - description: "Cursor AI agent for terminal-based coding assistance", - cwd: "", - commands: ["cursor-agent"], - }, - }, - { - name: "opencode", - preset: { - name: "opencode", - description: "OpenCode: Open source AI coding agent", - cwd: "", - commands: ["opencode"], - }, - }, -]; - -export function PresetsSettings() { - const { - presets: serverPresets, - isLoading, - createPreset, - updatePreset, - deletePreset, - setDefaultPreset, - } = usePresets(); - const [localPresets, setLocalPresets] = - useState(serverPresets); - const isDark = useIsDarkTheme(); - - useEffect(() => { - setLocalPresets(serverPresets); - }, [serverPresets]); - - const existingPresetNames = useMemo( - () => new Set(serverPresets.map((p) => p.name)), - [serverPresets], - ); - - const isTemplateAdded = (template: PresetTemplate) => - existingPresetNames.has(template.preset.name); - - const handleCellChange = ( - rowIndex: number, - column: PresetColumnKey, - value: string, - ) => { - setLocalPresets((prev) => - prev.map((p, i) => (i === rowIndex ? { ...p, [column]: value } : p)), - ); - }; - - const handleCellBlur = (rowIndex: number, column: PresetColumnKey) => { - const preset = localPresets[rowIndex]; - const serverPreset = serverPresets[rowIndex]; - if (!preset || !serverPreset) return; - if (preset[column] === serverPreset[column]) return; - - updatePreset.mutate({ - id: preset.id, - patch: { [column]: preset[column] }, - }); - }; - - const handleCommandsChange = (rowIndex: number, commands: string[]) => { - setLocalPresets((prev) => - prev.map((p, i) => (i === rowIndex ? { ...p, commands } : p)), - ); - }; - - const handleCommandsBlur = (rowIndex: number) => { - const preset = localPresets[rowIndex]; - const serverPreset = serverPresets[rowIndex]; - if (!preset || !serverPreset) return; - if ( - JSON.stringify(preset.commands) === JSON.stringify(serverPreset.commands) - ) - return; - - updatePreset.mutate({ - id: preset.id, - patch: { commands: preset.commands }, - }); - }; - - const handleAddRow = () => { - createPreset.mutate({ - name: "", - cwd: "", - commands: [""], - }); - }; - - const handleAddTemplate = (template: PresetTemplate) => { - if (isTemplateAdded(template)) return; - createPreset.mutate(template.preset); - }; - - const handleDeleteRow = (rowIndex: number) => { - const preset = localPresets[rowIndex]; - if (!preset) return; - - deletePreset.mutate({ id: preset.id }); - }; - - const handleSetDefault = (presetId: string | null) => { - setDefaultPreset.mutate({ id: presetId }); - }; - - if (isLoading) { - return ( -
-
-
-
-
-
- ); - } - - return ( -
-
-
-

Terminal Presets

- -
-

- Presets let you quickly launch terminals with pre-configured commands. - Create a preset below, then use it from the "New Terminal" dropdown in - any workspace. -

- -
- - Quick add: - - {PRESET_TEMPLATES.map((template) => { - const alreadyAdded = isTemplateAdded(template); - const presetIcon = getPresetIcon(template.name, isDark); - return ( - - - - - - {alreadyAdded ? "Already added" : template.preset.description} - - - ); - })} -
-
- -
-
- {PRESET_COLUMNS.map((column) => ( -
- {column.label} -
- ))} -
- Actions -
-
- -
- {localPresets.length > 0 ? ( - localPresets.map((preset, index) => ( - - )) - ) : ( -
- No presets yet. Click "Add Preset" to create your first preset. -
- )} -
-
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/index.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/index.ts deleted file mode 100644 index c1962bafc50..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PresetsSettings } from "./PresetsSettings"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/ProjectSettings/index.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/ProjectSettings/index.ts deleted file mode 100644 index 46b8b1f5e83..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/ProjectSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProjectSettings } from "./ProjectSettings"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/RingtonesSettings/index.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/RingtonesSettings/index.ts deleted file mode 100644 index 07f05ce9abc..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/RingtonesSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RingtonesSettings } from "./RingtonesSettings"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsContent.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsContent.tsx deleted file mode 100644 index b949cc29523..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsContent.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { SettingsSection } from "renderer/stores"; -import { AccountSettings } from "./AccountSettings"; -import { AppearanceSettings } from "./AppearanceSettings"; -import { BehaviorSettings } from "./BehaviorSettings"; -import { KeyboardShortcutsSettings } from "./KeyboardShortcutsSettings"; -import { PresetsSettings } from "./PresetsSettings"; -import { ProjectSettings } from "./ProjectSettings"; -import { RingtonesSettings } from "./RingtonesSettings"; -import { TeamSettings } from "./TeamSettings"; -import { WorkspaceSettings } from "./WorkspaceSettings"; - -interface SettingsContentProps { - activeSection: SettingsSection; -} - -export function SettingsContent({ activeSection }: SettingsContentProps) { - return ( -
- {activeSection === "account" && } - {activeSection === "project" && } - {activeSection === "workspace" && } - {activeSection === "team" && } - {activeSection === "appearance" && } - {activeSection === "ringtones" && } - {activeSection === "keyboard" && } - {activeSection === "presets" && } - {activeSection === "behavior" && } -
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/ProjectsSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/ProjectsSettings.tsx deleted file mode 100644 index e2427284aab..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/ProjectsSettings.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { cn } from "@superset/ui/utils"; -import { useEffect, useState } from "react"; -import { HiChevronDown, HiChevronRight } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; -import { useSetActiveWorkspace } from "renderer/react-query/workspaces"; -import type { SettingsSection } from "renderer/stores"; - -interface ProjectsSettingsProps { - activeSection: SettingsSection; - onSectionChange: (section: SettingsSection) => void; -} - -export function ProjectsSettings({ - activeSection, - onSectionChange, -}: ProjectsSettingsProps) { - const { data: groups = [] } = trpc.workspaces.getAllGrouped.useQuery(); - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); - const setActiveWorkspace = useSetActiveWorkspace(); - const [expandedProjects, setExpandedProjects] = useState>( - new Set(), - ); - - // Expand all projects by default when groups are loaded - useEffect(() => { - if (groups.length > 0) { - setExpandedProjects(new Set(groups.map((g) => g.project.id))); - } - }, [groups]); - - const toggleProject = (projectId: string) => { - setExpandedProjects((prev) => { - const next = new Set(prev); - if (next.has(projectId)) { - next.delete(projectId); - } else { - next.add(projectId); - } - return next; - }); - }; - - const handleProjectClick = (workspaceId: string) => { - // Set a workspace from this project as active to show project settings - setActiveWorkspace.mutate({ id: workspaceId }); - onSectionChange("project"); - }; - - const handleWorkspaceClick = (workspaceId: string) => { - setActiveWorkspace.mutate({ id: workspaceId }); - onSectionChange("workspace"); - }; - - if (groups.length === 0) { - return null; - } - - return ( -
-

- Projects -

- -
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/SettingsSidebar.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/SettingsSidebar.tsx deleted file mode 100644 index faf5413d66b..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar/SettingsSidebar.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { HiArrowLeft } from "react-icons/hi2"; -import { type SettingsSection, useCloseSettings } from "renderer/stores"; -import { GeneralSettings } from "./GeneralSettings"; -import { ProjectsSettings } from "./ProjectsSettings"; - -interface SettingsSidebarProps { - activeSection: SettingsSection; - onSectionChange: (section: SettingsSection) => void; -} - -export function SettingsSidebar({ - activeSection, - onSectionChange, -}: SettingsSidebarProps) { - const closeSettings = useCloseSettings(); - - return ( -
- {/* Back button */} - - - {/* Settings title */} -

Settings

- -
- - -
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberRow/MemberRow.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberRow/MemberRow.tsx deleted file mode 100644 index a6477eb6082..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberRow/MemberRow.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { authClient } from "@superset/auth/client"; -import { Avatar } from "@superset/ui/atoms/Avatar"; -import { Badge } from "@superset/ui/badge"; -import { Button } from "@superset/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@superset/ui/dialog"; -import { toast } from "@superset/ui/sonner"; -import { useState } from "react"; -import type { MemberDetails } from "../MemberActions"; - -interface MemberRowProps { - member: MemberDetails; - isCurrentUser: boolean; - canRemove: boolean; -} - -export function MemberRow({ - member, - isCurrentUser, - canRemove, -}: MemberRowProps) { - const [showRemoveDialog, setShowRemoveDialog] = useState(false); - const [isRemoving, setIsRemoving] = useState(false); - - const handleRemove = async () => { - setIsRemoving(true); - try { - await authClient.organization.removeMember({ - organizationId: member.organizationId, - memberIdOrEmail: member.userId, - }); - toast.success("Member removed"); - setShowRemoveDialog(false); - // Electric collections will automatically update via real-time sync - } catch (error) { - toast.error( - error instanceof Error ? error.message : "Failed to remove member", - ); - } finally { - setIsRemoving(false); - } - }; - - const isOwner = member.role === "owner"; - - return ( - <> -
- - -
-
-

{member.name || "Unknown"}

- {isCurrentUser && ( - - You - - )} -
-
-

- {member.email} -

- - {member.role} - -
-
- - {canRemove && ( - - )} -
- - - - - Remove team member? - - Are you sure you want to remove {member.name} ( - {member.email}) from the organization? They will lose access - immediately. - - - - - - - - - - ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberRow/index.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberRow/index.ts deleted file mode 100644 index a1e58f9addd..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/components/MemberRow/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MemberRow } from "./MemberRow"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/index.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/index.ts deleted file mode 100644 index 928d18b5fe6..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/TeamSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TeamSettings } from "./TeamSettings"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/WorkspaceSettings/index.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/WorkspaceSettings/index.ts deleted file mode 100644 index 52c413ca2c9..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/WorkspaceSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { WorkspaceSettings } from "./WorkspaceSettings"; diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/index.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/index.tsx deleted file mode 100644 index 69f02869f33..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { - useSetSettingsSection, - useSettingsSection, -} from "renderer/stores/app-state"; -import { SettingsContent } from "./SettingsContent"; -import { SettingsSidebar } from "./SettingsSidebar"; - -export function SettingsView() { - const activeSection = useSettingsSection(); - const setActiveSection = useSetSettingsSection(); - - return ( -
- -
- -
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx index cb9aa8489b4..6627b593376 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx @@ -11,11 +11,11 @@ import { import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { cn } from "@superset/ui/utils"; +import { useNavigate } from "@tanstack/react-router"; import { HiChevronRight, HiMiniPlus } from "react-icons/hi2"; import { LuFolderOpen, LuPalette, LuSettings, LuX } from "react-icons/lu"; import { trpc } from "renderer/lib/trpc"; import { useUpdateProject } from "renderer/react-query/projects/useUpdateProject"; -import { useOpenSettings } from "renderer/stores/app-state"; import { PROJECT_COLOR_DEFAULT, PROJECT_COLORS, @@ -51,7 +51,7 @@ export function ProjectHeader({ onNewWorkspace, }: ProjectHeaderProps) { const utils = trpc.useUtils(); - const openSettings = useOpenSettings(); + const navigate = useNavigate(); const closeProject = trpc.projects.close.useMutation({ onSuccess: (data) => { @@ -80,7 +80,7 @@ export function ProjectHeader({ }; const handleOpenSettings = () => { - openSettings("project"); + navigate({ to: "/settings/project/$projectId", params: { projectId } }); }; const updateProject = useUpdateProject({ diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx index 6a8ea219c7b..82659eea054 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx @@ -12,6 +12,7 @@ import { } from "@superset/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useLiveQuery } from "@tanstack/react-db"; +import { useNavigate } from "@tanstack/react-router"; import { HiCheck, HiChevronUpDown, @@ -20,7 +21,6 @@ import { import { trpc } from "renderer/lib/trpc"; import { useAuth } from "renderer/providers/AuthProvider"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; -import { useOpenSettings } from "renderer/stores/app-state"; interface OrganizationDropdownProps { isCollapsed?: boolean; @@ -33,7 +33,7 @@ export function OrganizationDropdown({ const collections = useCollections(); const setActiveOrg = trpc.auth.setActiveOrganization.useMutation(); const signOut = trpc.auth.signOut.useMutation(); - const openSettings = useOpenSettings(); + const navigate = useNavigate(); const activeOrganizationId = session?.session?.activeOrganizationId; @@ -100,12 +100,16 @@ export function OrganizationDropdown({ {activeOrganization && ( <> {/* Settings */} - openSettings()}> + navigate({ to: "/settings/account" })} + > Settings {/* Team management */} - openSettings("team")}> + navigate({ to: "/settings/team" })} + > Invite and manage members diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx index f0a26389c78..3b98e6bd134 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx @@ -8,6 +8,7 @@ import { DropdownMenuTrigger, } from "@superset/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { useNavigate } from "@tanstack/react-router"; import { useCallback, useMemo, useRef, useState } from "react"; import { HiMiniChevronDown, @@ -23,7 +24,6 @@ import { import { HotkeyTooltipContent } from "renderer/components/HotkeyTooltipContent"; import { trpc } from "renderer/lib/trpc"; import { usePresets } from "renderer/react-query/presets"; -import { useOpenSettings } from "renderer/stores"; import { useTabsStore } from "renderer/stores/tabs/store"; import { useTabsWithPresets } from "renderer/stores/tabs/useTabsWithPresets"; import { type ActivePaneStatus, pickHigherStatus } from "shared/tabs-types"; @@ -43,7 +43,7 @@ export function GroupStrip() { const { presets } = usePresets(); const isDark = useIsDarkTheme(); - const openSettings = useOpenSettings(); + const navigate = useNavigate(); const [dropdownOpen, setDropdownOpen] = useState(false); const hoverTimeoutRef = useRef | null>(null); @@ -111,7 +111,7 @@ export function GroupStrip() { }; const handleOpenPresetsSettings = () => { - openSettings("presets"); + navigate({ to: "/settings/presets" }); setDropdownOpen(false); }; diff --git a/apps/desktop/src/renderer/screens/main/index.tsx b/apps/desktop/src/renderer/screens/main/index.tsx index c413423d253..da47ba8c425 100644 --- a/apps/desktop/src/renderer/screens/main/index.tsx +++ b/apps/desktop/src/renderer/screens/main/index.tsx @@ -1,5 +1,6 @@ import { FEATURE_FLAGS } from "@superset/shared/constants"; import { Button } from "@superset/ui/button"; +import { useNavigate } from "@tanstack/react-router"; import { useFeatureFlagEnabled } from "posthog-js/react"; import { useCallback, useState } from "react"; import { HiArrowPath } from "react-icons/hi2"; @@ -9,7 +10,7 @@ import { UpdateRequiredPage } from "renderer/components/UpdateRequiredPage"; import { useUpdateListener } from "renderer/components/UpdateToast"; import { useVersionCheck } from "renderer/hooks/useVersionCheck"; import { trpc } from "renderer/lib/trpc"; -import { useCurrentView, useOpenSettings } from "renderer/stores/app-state"; +import { useCurrentView } from "renderer/stores/app-state"; import { useAppHotkey, useHotkeysSync } from "renderer/stores/hotkeys"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; import { useSidebarStore } from "renderer/stores/sidebar-state"; @@ -28,7 +29,6 @@ import { import { AppFrame } from "./components/AppFrame"; import { Background } from "./components/Background"; import { ResizablePanel } from "./components/ResizablePanel"; -import { SettingsView } from "./components/SettingsView"; import { StartView } from "./components/StartView"; import { TasksView } from "./components/TasksView"; import { TopBar } from "./components/TopBar"; @@ -64,7 +64,7 @@ export function MainScreen() { }); const currentView = useCurrentView(); - const openSettings = useOpenSettings(); + const navigate = useNavigate(); const openNewWorkspaceModal = useOpenNewWorkspaceModal(); const toggleSidebar = useSidebarStore((s) => s.toggleSidebar); const { @@ -103,7 +103,8 @@ export function MainScreen() { trpc.menu.subscribe.useSubscription(undefined, { onData: (event) => { if (event.type === "open-settings") { - openSettings(event.data.section); + const section = event.data.section || "account"; + navigate({ to: `/settings/${section}` as "/settings/account" }); } }, }); @@ -116,9 +117,12 @@ export function MainScreen() { const activeTab = tabs.find((t) => t.id === activeTabId); const isWorkspaceView = currentView === "workspace"; - useAppHotkey("SHOW_HOTKEYS", () => openSettings("keyboard"), undefined, [ - openSettings, - ]); + useAppHotkey( + "SHOW_HOTKEYS", + () => navigate({ to: "/settings/keyboard" }), + undefined, + [navigate], + ); useAppHotkey( "TOGGLE_SIDEBAR", @@ -244,8 +248,7 @@ export function MainScreen() { ); const isLoading = isWorkspaceLoading; - const showStartView = - !isLoading && !activeWorkspace && currentView !== "settings"; + const showStartView = !isLoading && !activeWorkspace; if (isVersionLoading) { return ( @@ -271,9 +274,6 @@ export function MainScreen() { } const renderContent = () => { - if (currentView === "settings") { - return ; - } if (currentView === "tasks" && hasTasksAccess) { return ; } diff --git a/apps/desktop/src/renderer/stores/app-state.ts b/apps/desktop/src/renderer/stores/app-state.ts index 47fe07d2763..e1dd4843560 100644 --- a/apps/desktop/src/renderer/stores/app-state.ts +++ b/apps/desktop/src/renderer/stores/app-state.ts @@ -1,29 +1,13 @@ import { create } from "zustand"; import { devtools } from "zustand/middleware"; -export type AppView = "workspace" | "settings" | "tasks" | "workspaces-list"; -export type SettingsSection = - | "account" - | "project" - | "workspace" - | "team" - | "appearance" - | "keyboard" - | "presets" - | "ringtones" - | "behavior"; +export type AppView = "workspace" | "tasks" | "workspaces-list"; interface AppState { currentView: AppView; - isSettingsTabOpen: boolean; isTasksTabOpen: boolean; isWorkspacesListOpen: boolean; - settingsSection: SettingsSection; setView: (view: AppView) => void; - openSettings: (section?: SettingsSection) => void; - closeSettings: () => void; - closeSettingsTab: () => void; - setSettingsSection: (section: SettingsSection) => void; openTasks: () => void; closeTasks: () => void; openWorkspacesList: () => void; @@ -34,35 +18,13 @@ export const useAppStore = create()( devtools( (set) => ({ currentView: "workspace", - isSettingsTabOpen: false, isTasksTabOpen: false, isWorkspacesListOpen: false, - settingsSection: "project", setView: (view) => { set({ currentView: view }); }, - openSettings: (section) => { - set({ - currentView: "settings", - isSettingsTabOpen: true, - ...(section && { settingsSection: section }), - }); - }, - - closeSettings: () => { - set({ currentView: "workspace" }); - }, - - closeSettingsTab: () => { - set({ currentView: "workspace", isSettingsTabOpen: false }); - }, - - setSettingsSection: (section) => { - set({ settingsSection: section }); - }, - openTasks: () => { set({ currentView: "tasks", isTasksTabOpen: true }); }, @@ -85,15 +47,6 @@ export const useAppStore = create()( // Convenience hooks export const useCurrentView = () => useAppStore((state) => state.currentView); -export const useIsSettingsTabOpen = () => - useAppStore((state) => state.isSettingsTabOpen); -export const useSettingsSection = () => - useAppStore((state) => state.settingsSection); -export const useSetSettingsSection = () => - useAppStore((state) => state.setSettingsSection); -export const useOpenSettings = () => useAppStore((state) => state.openSettings); -export const useCloseSettings = () => - useAppStore((state) => state.closeSettings); export const useOpenTasks = () => useAppStore((state) => state.openTasks); export const useOpenWorkspacesList = () => useAppStore((state) => state.openWorkspacesList);