);
}
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 */}
+
+
+
+ Confirm before quitting
+
+
+ Show a confirmation dialog when quitting the app
+
+
+
+
+
+
+
+
+ Terminal file links
+
+
+ Choose how to open file paths when Cmd+clicking in the terminal
+
+
+
+
+
+
+
+ External editor
+ File viewer
+
+
+
+
);
}
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
- {GENERAL_SECTIONS.map((section) => (
- onSectionChange(section.id)}
- className={cn(
- "flex items-center gap-3 px-3 py-1.5 text-sm rounded-md transition-colors text-left",
- activeSection === section.id
- ? "bg-accent text-accent-foreground"
- : "text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground",
- )}
- >
- {section.icon}
- {section.label}
-
- ))}
+ {GENERAL_SECTIONS.map((section) => {
+ const isActive = matchRoute({ to: section.id });
+
+ return (
+
+ {section.icon}
+ {section.label}
+
+ );
+ })}
);
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
+
+
+ {groups.map((group) => {
+ const isProjectActive = matchRoute({
+ to: "/settings/project/$projectId",
+ params: { projectId: group.project.id },
+ });
+
+ return (
+
+ {/* Project header */}
+
+
+
+
+ {group.project.name}
+
+
+
toggleProject(group.project.id)}
+ className={cn(
+ "px-2 h-full flex items-center",
+ isProjectActive
+ ? "text-accent-foreground"
+ : "text-muted-foreground",
+ )}
+ >
+ {expandedProjects.has(group.project.id) ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* Workspaces */}
+ {expandedProjects.has(group.project.id) && (
+
+ {group.workspaces.map((workspace) => {
+ const isWorkspaceActive = matchRoute({
+ to: "/settings/workspace/$workspaceId",
+ params: { workspaceId: workspace.id },
+ });
+
+ return (
+
+ {workspace.name}
+
+ );
+ })}
+
+ )}
+
+ );
+ })}
+
+
+ );
+}
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}
+ )}
+
+
+
+ {isRecording ? (
+ Recording…
+ ) : (
+
+ {display.map((key) => (
+ {key}
+ ))}
+
+ )}
+
+
+ Reset
+
+
+
+ );
+}
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.
+
+
+
+
+ Import
+
+
+ Export
+
+ {
+ setRecordingId(null);
+ resetAllHotkeys();
+ }}
+ >
+ Reset all
+
+
+
+
+ {/* 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?
+
+
+
+
+ setPendingConflict(null)}
+ >
+ Cancel
+
+
+ Reassign
+
+
+
+
+
+ {/* 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}.
+
+ )}
+
+
+
+
+ setPendingImport(null)}
+ >
+ Cancel
+
+
+ Import
+
+
+
+
);
}
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
+
+
+ Add Preset
+
+
+
+ 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 (
+
+
+ handleAddTemplate(template)}
+ disabled={alreadyAdded || createPreset.isPending}
+ >
+ {alreadyAdded ? (
+
+ ) : presetIcon ? (
+
+ ) : null}
+ {template.name}
+
+
+
+ {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() {
openSettings()}
+ onClick={() => navigate({ to: "/settings/account" })}
aria-label="Open settings"
className="no-drag"
>
diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/AccountSettings/AccountSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/AccountSettings/AccountSettings.tsx
deleted file mode 100644
index d97507614a3..00000000000
--- a/apps/desktop/src/renderer/screens/main/components/SettingsView/AccountSettings/AccountSettings.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-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 { trpc } from "renderer/lib/trpc";
-
-export function AccountSettings() {
- 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
-
- 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.
-
-
signOut()}>
- Sign Out
-
-
-
-
- );
-}
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
-
-
setMarkdownStyle(value as MarkdownStyle)}
- >
-
-
-
-
- Default
- Tufte
-
-
-
- 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 */}
-
-
-
- Confirm before quitting
-
-
- Show a confirmation dialog when quitting the app
-
-
-
-
-
-
-
-
- Terminal file links
-
-
- Choose how to open file paths when Cmd+clicking in the terminal
-
-
-
-
-
-
-
- External editor
- File viewer
-
-
-
-
-
- );
-}
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}
- )}
-
-
-
- {isRecording ? (
- Recording…
- ) : (
-
- {display.map((key) => (
- {key}
- ))}
-
- )}
-
-
- Reset
-
-
-
- );
-}
-
-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.
-
-
-
-
- Import
-
-
- Export
-
- {
- setRecordingId(null);
- resetAllHotkeys();
- }}
- >
- Reset all
-
-
-
-
- {/* 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?
-
-
-
-
- setPendingConflict(null)}
- >
- Cancel
-
-
- Reassign
-
-
-
-
-
- {/* 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}.
-
- )}
-
-
-
-
- setPendingImport(null)}
- >
- Cancel
-
-
- Import
-
-
-
-
-
- );
-}
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
-
-
- Add Preset
-
-
-
- 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 (
-
-
- handleAddTemplate(template)}
- disabled={alreadyAdded || createPreset.isPending}
- >
- {alreadyAdded ? (
-
- ) : presetIcon ? (
-
- ) : null}
- {template.name}
-
-
-
- {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
-
-
- {groups.map((group) => (
-
- {/* Project header */}
-
-
- handleProjectClick(group.workspaces[0]?.id ?? "")
- }
- className="flex-1 flex items-center gap-2 pl-3 pr-1 h-full text-sm text-left"
- >
-
-
- {group.project.name}
-
-
-
toggleProject(group.project.id)}
- className={cn(
- "px-2 h-full flex items-center",
- activeWorkspace?.projectId === group.project.id &&
- activeSection === "project"
- ? "text-accent-foreground"
- : "text-muted-foreground",
- )}
- >
- {expandedProjects.has(group.project.id) ? (
-
- ) : (
-
- )}
-
-
-
- {/* Workspaces */}
- {expandedProjects.has(group.project.id) && (
-
- {group.workspaces.map((workspace) => (
- handleWorkspaceClick(workspace.id)}
- className={cn(
- "flex items-center gap-2 px-2 py-1 text-sm w-full text-left rounded-md transition-colors",
- activeWorkspace?.id === workspace.id &&
- activeSection === "workspace"
- ? "bg-accent text-accent-foreground"
- : "text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground",
- )}
- >
- {workspace.name}
-
- ))}
-
- )}
-
- ))}
-
-
- );
-}
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 */}
-
-
- Back
-
-
- {/* 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 && (
-
setShowRemoveDialog(true)}
- className="shrink-0"
- >
- Remove
-
- )}
-
-
-
-
-
- Remove team member?
-
- Are you sure you want to remove {member.name} (
- {member.email}) from the organization? They will lose access
- immediately.
-
-
-
- setShowRemoveDialog(false)}
- disabled={isRemoving}
- >
- Cancel
-
-
- {isRemoving ? "Removing..." : "Remove Member"}
-
-
-
-
- >
- );
-}
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);