Skip to content
Merged
24 changes: 23 additions & 1 deletion apps/desktop/src/lib/trpc/routers/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { db } from "main/lib/db";
import { z } from "zod";
Expand Down Expand Up @@ -77,6 +77,28 @@ export const createConfigRouter = () => {
}
return ensureConfigExists(project.mainRepoPath);
}),

// Get the config file content
getConfigContent: publicProcedure
.input(z.object({ projectId: z.string() }))
.query(({ input }) => {
const project = db.data.projects.find((p) => p.id === input.projectId);
if (!project) {
return { content: null, exists: false };
}

const configPath = getConfigPath(project.mainRepoPath);
if (!existsSync(configPath)) {
return { content: null, exists: false };
}

try {
const content = readFileSync(configPath, "utf-8");
return { content, exists: true };
} catch {
return { content: null, exists: false };
}
}),
});
};

Expand Down
17 changes: 17 additions & 0 deletions apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,26 @@ export const createWorkspacesRouter = () => {
);
}

const project = db.data.projects.find(
(p) => p.id === workspace.projectId,
);
const worktree = db.data.worktrees.find(
(wt) => wt.id === workspace.worktreeId,
);

return {
...workspace,
worktreePath: getWorktreePath(workspace.worktreeId) ?? "",
project: project
? {
id: project.id,
name: project.name,
mainRepoPath: project.mainRepoPath,
}
: null,
worktree: worktree
? { branch: worktree.branch, gitStatus: worktree.gitStatus }
: null,
};
}),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Button } from "@superset/ui/button";
import { cn } from "@superset/ui/utils";
import { HiArrowTopRightOnSquare } from "react-icons/hi2";
import { OpenInButton } from "renderer/components/OpenInButton";
import { trpc } from "renderer/lib/trpc";
import {
CONFIG_FILE_NAME,
CONFIG_TEMPLATE,
PROJECT_SUPERSET_DIR_NAME,
WEBSITE_URL,
} from "shared/constants";

export interface ConfigFilePreviewProps {
projectId: string;
projectName: string;
configFilePath?: string;
className?: string;
}

export function ConfigFilePreview({
projectId,
projectName,
configFilePath,
className,
}: ConfigFilePreviewProps) {
const { data: configData } = trpc.config.getConfigContent.useQuery(
{ projectId },
{ enabled: !!projectId },
);

const handleLearnMore = () => {
window.open(`${WEBSITE_URL}/scripts`, "_blank");
};

const displayContent =
configData?.exists && configData.content
? configData.content
: CONFIG_TEMPLATE;

return (
<>
<div
className={cn(
"rounded-lg border border-border bg-card overflow-hidden",
className,
)}
>
<div className="flex items-center justify-between gap-4 px-4 py-3 border-b border-border">
<span className="text-sm text-muted-foreground font-mono truncate">
{projectName}/{PROJECT_SUPERSET_DIR_NAME}/{CONFIG_FILE_NAME}
</span>
<OpenInButton path={configFilePath} label={CONFIG_FILE_NAME} />
</div>

<div className="p-4 bg-background/50">
<pre className="text-sm font-mono text-foreground leading-relaxed whitespace-pre-wrap">
{displayContent}
</pre>
</div>
</div>

<div className="mt-4">
<Button
variant="outline"
size="sm"
onClick={handleLearnMore}
className="gap-2"
>
Learn how to use scripts
<HiArrowTopRightOnSquare className="h-4 w-4" />
</Button>
</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
ConfigFilePreview,
type ConfigFilePreviewProps,
} from "./ConfigFilePreview";
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useSetTheme, useThemeId, useThemeStore } from "renderer/stores";
import { builtInThemes } from "shared/themes";
import { ThemeCard } from "./components/ThemeCard";
import { ThemeCard } from "./ThemeCard";

export function AppearanceSettings() {
const activeThemeId = useThemeId();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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();

const { data: configFilePath } = trpc.config.getConfigFilePath.useQuery(
{ projectId: activeWorkspace?.projectId ?? "" },
{ enabled: !!activeWorkspace?.projectId },
);

if (isLoading) {
return (
<div className="p-6 max-w-4xl select-text">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-muted rounded w-1/3" />
<div className="h-4 bg-muted rounded w-1/2" />
</div>
</div>
);
}

if (!activeWorkspace?.project) {
return (
<div className="p-6 max-w-4xl">
<div className="mb-8">
<h2 className="text-xl font-semibold">Project</h2>
<p className="text-sm text-muted-foreground mt-1">
No active project selected
</p>
</div>
</div>
);
}

const { project } = activeWorkspace;

return (
<div className="p-6 max-w-4xl w-full select-text">
<div className="mb-8">
<h2 className="text-xl font-semibold">Project</h2>
</div>

<div className="space-y-6">
<div className="space-y-2">
<h3 className="text-base font-semibold text-foreground">Name</h3>
<p>{project.name}</p>
</div>

<div className="space-y-2">
<h3 className="text-base font-semibold text-foreground flex items-center gap-2">
<HiOutlineFolder className="h-4 w-4" />
Repository Path
</h3>
<p className="text-sm font-mono break-all">{project.mainRepoPath}</p>
</div>

<div className="pt-4 border-t space-y-4">
<div className="space-y-2">
<h3 className="text-base font-semibold text-foreground flex items-center gap-2">
<HiOutlineCog6Tooth className="h-4 w-4" />
Scripts
</h3>
<p className="text-sm text-muted-foreground">
Configure setup and teardown scripts that run when workspaces are
created or deleted.
</p>
</div>
<ConfigFilePreview
projectId={project.id}
projectName={project.name}
configFilePath={configFilePath ?? undefined}
/>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ProjectSettings } from "./ProjectSettings";
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { SettingsSection } from "renderer/stores";
import { AppearanceSettings } from "./AppearanceSettings";
import { KeyboardShortcutsSettings } from "./KeyboardShortcutsSettings";
import { ProjectSettings } from "./ProjectSettings";
import { WorkspaceSettings } from "./WorkspaceSettings";

interface SettingsContentProps {
activeSection: SettingsSection;
Expand All @@ -9,6 +11,8 @@ interface SettingsContentProps {
export function SettingsContent({ activeSection }: SettingsContentProps) {
return (
<div className="h-full overflow-y-auto flex justify-center">
{activeSection === "project" && <ProjectSettings />}
{activeSection === "workspace" && <WorkspaceSettings />}
{activeSection === "appearance" && <AppearanceSettings />}
{activeSection === "keyboard" && <KeyboardShortcutsSettings />}
</div>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { cn } from "@superset/ui/utils";
import { HiOutlineCommandLine, HiOutlinePaintBrush } from "react-icons/hi2";
import type { SettingsSection } from "renderer/stores";

interface GeneralSettingsProps {
activeSection: SettingsSection;
onSectionChange: (section: SettingsSection) => void;
}

const GENERAL_SECTIONS: {
id: SettingsSection;
label: string;
icon: React.ReactNode;
}[] = [
{
id: "appearance",
label: "Appearance",
icon: <HiOutlinePaintBrush className="h-4 w-4" />,
},
{
id: "keyboard",
label: "Keyboard Shortcuts",
icon: <HiOutlineCommandLine className="h-4 w-4" />,
},
];

export function GeneralSettings({
activeSection,
onSectionChange,
}: GeneralSettingsProps) {
return (
<div className="mb-4">
<h2 className="text-xs font-medium text-muted-foreground uppercase tracking-wider px-3 mb-2">
General
</h2>
<nav className="flex flex-col gap-0.5">
{GENERAL_SECTIONS.map((section) => (
<button
key={section.id}
type="button"
onClick={() => 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}
</button>
))}
</nav>
</div>
);
}
Loading