From b91f5a246df3260306579c6a59feb543f46969f6 Mon Sep 17 00:00:00 2001 From: Ipriyankrajai Date: Sun, 8 Feb 2026 18:43:10 +0530 Subject: [PATCH 1/3] feat(desktop): add project color mode functionality - Introduced a new `colorMode` property for projects, allowing users to choose between "border" and "background" styles. - Updated project settings UI to include color mode selection. - Modified project-related components to support and display the selected color mode. - Added database migration to include the new `color_mode` column in the projects table. - Enhanced project thumbnail rendering based on the selected color mode. This feature improves the customization options for project appearances in the application. --- .../src/lib/trpc/routers/projects/projects.ts | 17 +- .../routers/workspaces/procedures/query.ts | 42 +- .../ProjectSettings/ProjectSettings.tsx | 117 +- .../ProjectSection/ProjectHeader.tsx | 29 + .../ProjectSection/ProjectSection.tsx | 4 + .../ProjectThumbnail/ProjectThumbnail.tsx | 71 +- .../WorkspaceSidebar/WorkspaceSidebar.tsx | 1 + .../src/shared/constants/project-colors.ts | 9 + .../0020_add_color_mode_to_projects.sql | 1 + .../local-db/drizzle/meta/0020_snapshot.json | 1078 +++++++++++++++++ packages/local-db/drizzle/meta/_journal.json | 7 + packages/local-db/src/schema/schema.ts | 2 + packages/local-db/src/schema/zod.ts | 4 + 13 files changed, 1301 insertions(+), 81 deletions(-) create mode 100644 packages/local-db/drizzle/0020_add_color_mode_to_projects.sql create mode 100644 packages/local-db/drizzle/meta/0020_snapshot.json diff --git a/apps/desktop/src/lib/trpc/routers/projects/projects.ts b/apps/desktop/src/lib/trpc/routers/projects/projects.ts index 45edfbc7191..f5b508f2e16 100644 --- a/apps/desktop/src/lib/trpc/routers/projects/projects.ts +++ b/apps/desktop/src/lib/trpc/routers/projects/projects.ts @@ -3,6 +3,7 @@ import { access } from "node:fs/promises"; import { basename, join } from "node:path"; import { BRANCH_PREFIX_MODES, + PROJECT_COLOR_MODES, projects, type SelectProject, settings, @@ -778,9 +779,10 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { "Invalid project color", ) .optional(), - branchPrefixMode: z.enum(BRANCH_PREFIX_MODES).nullable().optional(), - branchPrefixCustom: z.string().nullable().optional(), - hideImage: z.boolean().optional(), + branchPrefixMode: z.enum(BRANCH_PREFIX_MODES).nullable().optional(), + branchPrefixCustom: z.string().nullable().optional(), + hideImage: z.boolean().optional(), + colorMode: z.enum(PROJECT_COLOR_MODES).nullable().optional(), }), }), ) @@ -807,9 +809,12 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { ...(input.patch.branchPrefixCustom !== undefined && { branchPrefixCustom: input.patch.branchPrefixCustom, }), - ...(input.patch.hideImage !== undefined && { - hideImage: input.patch.hideImage, - }), + ...(input.patch.hideImage !== undefined && { + hideImage: input.patch.hideImage, + }), + ...(input.patch.colorMode !== undefined && { + colorMode: input.patch.colorMode, + }), lastOpenedAt: Date.now(), }) .where(eq(projects.id, input.id)) 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 70c7307750a..c33e4ccad8f 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts @@ -152,15 +152,16 @@ export const createQueryProcedures = () => { const groupsMap = new Map< string, { - project: { - id: string; - name: string; - color: string; - tabOrder: number; - githubOwner: string | null; - mainRepoPath: string; - hideImage: boolean; - }; + project: { + id: string; + name: string; + color: string; + colorMode: "border" | "background"; + tabOrder: number; + githubOwner: string | null; + mainRepoPath: string; + hideImage: boolean; + }; workspaces: Array<{ id: string; projectId: string; @@ -180,17 +181,18 @@ export const createQueryProcedures = () => { >(); for (const project of activeProjects) { - groupsMap.set(project.id, { - project: { - id: project.id, - name: project.name, - color: project.color, - // biome-ignore lint/style/noNonNullAssertion: filter guarantees tabOrder is not null - tabOrder: project.tabOrder!, - githubOwner: project.githubOwner ?? null, - mainRepoPath: project.mainRepoPath, - hideImage: project.hideImage ?? false, - }, + groupsMap.set(project.id, { + project: { + id: project.id, + name: project.name, + color: project.color, + colorMode: project.colorMode ?? "border", + // biome-ignore lint/style/noNonNullAssertion: filter guarantees tabOrder is not null + tabOrder: project.tabOrder!, + githubOwner: project.githubOwner ?? null, + mainRepoPath: project.mainRepoPath, + hideImage: project.hideImage ?? false, + }, workspaces: [], }); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx index e0e9cb3a27e..372c63b0416 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx @@ -14,8 +14,10 @@ import type { ReactNode } from "react"; import { useEffect, useState } from "react"; import { HiOutlineCog6Tooth, HiOutlinePaintBrush } from "react-icons/hi2"; import { electronTrpc } from "renderer/lib/electron-trpc"; +import type { ProjectColorMode } from "@superset/local-db"; import { PROJECT_COLOR_DEFAULT, + PROJECT_COLOR_MODE_LABELS, PROJECT_COLORS, } from "shared/constants/project-colors"; import { resolveBranchPrefix, sanitizeSegment } from "shared/utils/branch"; @@ -214,49 +216,88 @@ export function ProjectSettings({ projectId }: ProjectSettingsProps) { title="Appearance" description="Customize this project's sidebar look." > -
-
- {PROJECT_COLORS.map((color) => { - const isDefault = color.value === PROJECT_COLOR_DEFAULT; - const isSelected = project.color === color.value; - return ( -
+
+ + + updateProject.mutate({ + id: projectId, + patch: { hideImage: checked }, + }) + } + /> +
-
- - + +
+
+ +

+ Apply the project color to the badge border or background. +

+
+
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 4d9c2a5cf00..ff66c8da490 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 @@ -27,8 +27,10 @@ import { electronTrpc } from "renderer/lib/electron-trpc"; import { useUpdateProject } from "renderer/react-query/projects/useUpdateProject"; import { navigateToWorkspace } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation"; import { useProjectRename } from "renderer/screens/main/hooks/useProjectRename"; +import type { ProjectColorMode } from "@superset/local-db"; import { PROJECT_COLOR_DEFAULT, + PROJECT_COLOR_MODE_LABELS, PROJECT_COLORS, } from "shared/constants/project-colors"; import { STROKE_WIDTH } from "../constants"; @@ -40,6 +42,7 @@ interface ProjectHeaderProps { projectId: string; projectName: string; projectColor: string; + colorMode: ProjectColorMode; githubOwner: string | null; mainRepoPath: string; hideImage: boolean; @@ -56,6 +59,7 @@ export function ProjectHeader({ projectId, projectName, projectColor, + colorMode, githubOwner, mainRepoPath, hideImage, @@ -150,6 +154,10 @@ export function ProjectHeader({ updateProject.mutate({ id: projectId, patch: { hideImage: !hideImage } }); }; + const handleColorModeChange = (mode: ProjectColorMode) => { + updateProject.mutate({ id: projectId, patch: { colorMode: mode } }); + }; + // Color picker submenu used in both collapsed and expanded context menus const colorPickerSubmenu = ( @@ -180,6 +188,24 @@ export function ProjectHeader({ ); })} + + {( + Object.entries(PROJECT_COLOR_MODE_LABELS) as [ + ProjectColorMode, + string, + ][] + ).map(([mode, label]) => ( + handleColorModeChange(mode)} + className="flex items-center gap-2" + > + {label} + {colorMode === mode && ( + + )} + + ))} ); @@ -204,6 +230,7 @@ export function ProjectHeader({ projectId={projectId} projectName={projectName} projectColor={projectColor} + colorMode={colorMode} githubOwner={githubOwner} /> @@ -277,6 +304,7 @@ export function ProjectHeader({ projectId={projectId} projectName={projectName} projectColor={projectColor} + colorMode={colorMode} githubOwner={githubOwner} hideImage={hideImage} /> @@ -299,6 +327,7 @@ export function ProjectHeader({ projectId={projectId} projectName={projectName} projectColor={projectColor} + colorMode={colorMode} githubOwner={githubOwner} hideImage={hideImage} /> diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx index a791df1da9d..04612d0e6dc 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx @@ -26,6 +26,7 @@ interface ProjectSectionProps { projectId: string; projectName: string; projectColor: string; + colorMode: "border" | "background"; githubOwner: string | null; mainRepoPath: string; hideImage: boolean; @@ -42,6 +43,7 @@ export function ProjectSection({ projectId, projectName, projectColor, + colorMode, githubOwner, mainRepoPath, hideImage, @@ -140,6 +142,7 @@ export function ProjectSection({ projectId={projectId} projectName={projectName} projectColor={projectColor} + colorMode={colorMode} githubOwner={githubOwner} mainRepoPath={mainRepoPath} hideImage={hideImage} @@ -197,6 +200,7 @@ export function ProjectSection({ projectId={projectId} projectName={projectName} projectColor={projectColor} + colorMode={colorMode} githubOwner={githubOwner} mainRepoPath={mainRepoPath} hideImage={hideImage} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx index 5fe236905d6..d61c5c590da 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx @@ -1,3 +1,4 @@ +import type { ProjectColorMode } from "@superset/local-db"; import { cn } from "@superset/ui/utils"; import { useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; @@ -7,6 +8,7 @@ interface ProjectThumbnailProps { projectId: string; projectName: string; projectColor: string; + colorMode?: ProjectColorMode; githubOwner: string | null; hideImage?: boolean; className?: string; @@ -33,10 +35,28 @@ function isCustomColor(color: string): boolean { return color !== PROJECT_COLOR_DEFAULT && color.startsWith("#"); } +function getRelativeLuminance(hex: string): number { + const toLinear = (c: number) => { + const sRGB = c / 255; + return sRGB <= 0.03928 ? sRGB / 12.92 : ((sRGB + 0.055) / 1.055) ** 2.4; + }; + const r = toLinear(Number.parseInt(hex.slice(1, 3), 16)); + const g = toLinear(Number.parseInt(hex.slice(3, 5), 16)); + const b = toLinear(Number.parseInt(hex.slice(5, 7), 16)); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +function getContrastTextColor(hex: string): string { + return getRelativeLuminance(hex) > 0.4 + ? "rgba(0, 0, 0, 0.85)" + : "rgba(255, 255, 255, 0.95)"; +} + export function ProjectThumbnail({ projectId, projectName, projectColor, + colorMode = "border", githubOwner, hideImage, className, @@ -54,22 +74,28 @@ export function ProjectThumbnail({ const owner = avatarData?.owner ?? githubOwner; const firstLetter = projectName.charAt(0).toUpperCase(); const hasCustomColor = isCustomColor(projectColor); + const isBackground = colorMode === "background"; - // Border: gray by default, custom color with slight transparency when set + // Border mode: gray by default, custom color with slight transparency when set + // Background mode: no border const borderClasses = cn( - "border-[1.5px]", - hasCustomColor ? undefined : "border-border", + isBackground ? undefined : "border-[1.5px]", + !isBackground && !hasCustomColor && "border-border", ); - const borderStyle = hasCustomColor - ? { borderColor: hexToRgba(projectColor, 0.6) } - : undefined; + + const getBorderStyle = () => { + if (isBackground || !hasCustomColor) return undefined; + return { borderColor: hexToRgba(projectColor, 0.6) }; + }; + + const borderStyle = getBorderStyle(); // Show GitHub avatar if available and not hidden if (owner && !imageError && !hideImage) { return (
{ + if (!hasCustomColor) return borderStyle; + + if (isBackground) { + return { + backgroundColor: projectColor, + color: getContrastTextColor(projectColor), + }; + } + + return { + borderColor: hexToRgba(projectColor, 0.6), + backgroundColor: hexToRgba(projectColor, 0.15), + color: projectColor, + }; + }; + + const fallbackStyle = getFallbackStyle(); return (
color.value); + +export const PROJECT_COLOR_MODE_DEFAULT: ProjectColorMode = "border"; + +export const PROJECT_COLOR_MODE_LABELS: Record = { + border: "Border", + background: "Background", +}; diff --git a/packages/local-db/drizzle/0020_add_color_mode_to_projects.sql b/packages/local-db/drizzle/0020_add_color_mode_to_projects.sql new file mode 100644 index 00000000000..5e528777dcf --- /dev/null +++ b/packages/local-db/drizzle/0020_add_color_mode_to_projects.sql @@ -0,0 +1 @@ +ALTER TABLE `projects` ADD `color_mode` text; \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/0020_snapshot.json b/packages/local-db/drizzle/meta/0020_snapshot.json new file mode 100644 index 00000000000..7d30f01e5b7 --- /dev/null +++ b/packages/local-db/drizzle/meta/0020_snapshot.json @@ -0,0 +1,1078 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "74f65e6d-8d17-4c3a-8a36-1c8abf5552bb", + "prevId": "732c942c-5f01-451f-a6cf-92c38b434076", + "tables": { + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organization_members_organization_id_idx": { + "name": "organization_members_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_members_user_id_idx": { + "name": "organization_members_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_org_id": { + "name": "clerk_org_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_org": { + "name": "github_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_clerk_org_id_unique": { + "name": "organizations_clerk_org_id_unique", + "columns": [ + "clerk_org_id" + ], + "isUnique": true + }, + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "organizations_clerk_org_id_idx": { + "name": "organizations_clerk_org_id_idx", + "columns": [ + "clerk_org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "main_repo_path": { + "name": "main_repo_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_toast_dismissed": { + "name": "config_toast_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_owner": { + "name": "github_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hide_image": { + "name": "hide_image", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color_mode": { + "name": "color_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "projects_main_repo_path_idx": { + "name": "projects_main_repo_path_idx", + "columns": [ + "main_repo_path" + ], + "isUnique": false + }, + "projects_last_opened_at_idx": { + "name": "projects_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_app": { + "name": "last_used_app", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets": { + "name": "terminal_presets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets_initialized": { + "name": "terminal_presets_initialized", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "selected_ringtone_id": { + "name": "selected_ringtone_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_on_quit": { + "name": "confirm_on_quit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_link_behavior": { + "name": "terminal_link_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persist_terminal": { + "name": "persist_terminal", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "auto_apply_default_preset": { + "name": "auto_apply_default_preset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_sounds_muted": { + "name": "notification_sounds_muted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delete_local_branch": { + "name": "delete_local_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_color": { + "name": "status_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_type": { + "name": "status_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_position": { + "name": "status_position", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "labels": { + "name": "labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_provider": { + "name": "external_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_slug_unique": { + "name": "tasks_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + "assignee_id" + ], + "isUnique": false + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_id": { + "name": "clerk_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "columns": [ + "clerk_id" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "users_clerk_id_idx": { + "name": "users_clerk_id_idx", + "columns": [ + "clerk_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspaces": { + "name": "workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_unread": { + "name": "is_unread", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "is_unnamed": { + "name": "is_unnamed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "deleting_at": { + "name": "deleting_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "workspaces_worktree_id_idx": { + "name": "workspaces_worktree_id_idx", + "columns": [ + "worktree_id" + ], + "isUnique": false + }, + "workspaces_last_opened_at_idx": { + "name": "workspaces_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_worktree_id_worktrees_id_fk": { + "name": "workspaces_worktree_id_worktrees_id_fk", + "tableFrom": "workspaces", + "tableTo": "worktrees", + "columnsFrom": [ + "worktree_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "worktrees": { + "name": "worktrees", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "git_status": { + "name": "git_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_status": { + "name": "github_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "worktrees_project_id_idx": { + "name": "worktrees_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "worktrees_branch_idx": { + "name": "worktrees_branch_idx", + "columns": [ + "branch" + ], + "isUnique": false + } + }, + "foreignKeys": { + "worktrees_project_id_projects_id_fk": { + "name": "worktrees_project_id_projects_id_fk", + "tableFrom": "worktrees", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/_journal.json b/packages/local-db/drizzle/meta/_journal.json index d6ac4a2497b..f733fd584db 100644 --- a/packages/local-db/drizzle/meta/_journal.json +++ b/packages/local-db/drizzle/meta/_journal.json @@ -141,6 +141,13 @@ "when": 1770438863796, "tag": "0019_add_hide_image_to_projects", "breakpoints": true + }, + { + "idx": 20, + "version": "6", + "when": 1770555633680, + "tag": "0020_add_color_mode_to_projects", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/local-db/src/schema/schema.ts b/packages/local-db/src/schema/schema.ts index 74757311d4d..aa0d26204af 100644 --- a/packages/local-db/src/schema/schema.ts +++ b/packages/local-db/src/schema/schema.ts @@ -6,6 +6,7 @@ import type { ExternalApp, GitHubStatus, GitStatus, + ProjectColorMode, TerminalLinkBehavior, TerminalPreset, WorkspaceType, @@ -38,6 +39,7 @@ export const projects = sqliteTable( branchPrefixMode: text("branch_prefix_mode").$type(), branchPrefixCustom: text("branch_prefix_custom"), hideImage: integer("hide_image", { mode: "boolean" }), + colorMode: text("color_mode").$type(), }, (table) => [ index("projects_main_repo_path_idx").on(table.mainRepoPath), diff --git a/packages/local-db/src/schema/zod.ts b/packages/local-db/src/schema/zod.ts index bd91026d78e..5404c3f6d36 100644 --- a/packages/local-db/src/schema/zod.ts +++ b/packages/local-db/src/schema/zod.ts @@ -126,3 +126,7 @@ export const BRANCH_PREFIX_MODES = [ ] as const; export type BranchPrefixMode = (typeof BRANCH_PREFIX_MODES)[number]; + +export const PROJECT_COLOR_MODES = ["border", "background"] as const; + +export type ProjectColorMode = (typeof PROJECT_COLOR_MODES)[number]; From e780c2b34e3f3a9f5f2aab08834ae4630ab5d0a3 Mon Sep 17 00:00:00 2001 From: Ipriyankrajai Date: Sun, 8 Feb 2026 20:08:38 +0530 Subject: [PATCH 2/3] fix: cr comment --- .../WorkspaceSidebar/ProjectSection/ProjectSection.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx index 04612d0e6dc..bd37f9b318e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx @@ -1,3 +1,4 @@ +import type { ProjectColorMode } from "@superset/local-db"; import { toast } from "@superset/ui/sonner"; import { cn } from "@superset/ui/utils"; import { AnimatePresence, motion } from "framer-motion"; @@ -26,7 +27,7 @@ interface ProjectSectionProps { projectId: string; projectName: string; projectColor: string; - colorMode: "border" | "background"; + colorMode: ProjectColorMode; githubOwner: string | null; mainRepoPath: string; hideImage: boolean; From 8dcfbc2acdc826d2ba95a132d489c5edeb240e0f Mon Sep 17 00:00:00 2001 From: Ipriyankrajai Date: Sun, 8 Feb 2026 20:14:21 +0530 Subject: [PATCH 3/3] fix: nit cmt --- .../src/lib/trpc/routers/projects/projects.ts | 20 ++++---- .../routers/workspaces/procedures/query.ts | 46 +++++++++--------- .../ProjectSettings/ProjectSettings.tsx | 10 ++-- .../ProjectSection/ProjectHeader.tsx | 2 +- .../ProjectThumbnail/ProjectThumbnail.tsx | 47 +++++++++---------- 5 files changed, 60 insertions(+), 65 deletions(-) diff --git a/apps/desktop/src/lib/trpc/routers/projects/projects.ts b/apps/desktop/src/lib/trpc/routers/projects/projects.ts index f5b508f2e16..85b02b95674 100644 --- a/apps/desktop/src/lib/trpc/routers/projects/projects.ts +++ b/apps/desktop/src/lib/trpc/routers/projects/projects.ts @@ -779,10 +779,10 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { "Invalid project color", ) .optional(), - branchPrefixMode: z.enum(BRANCH_PREFIX_MODES).nullable().optional(), - branchPrefixCustom: z.string().nullable().optional(), - hideImage: z.boolean().optional(), - colorMode: z.enum(PROJECT_COLOR_MODES).nullable().optional(), + branchPrefixMode: z.enum(BRANCH_PREFIX_MODES).nullable().optional(), + branchPrefixCustom: z.string().nullable().optional(), + hideImage: z.boolean().optional(), + colorMode: z.enum(PROJECT_COLOR_MODES).nullable().optional(), }), }), ) @@ -809,12 +809,12 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { ...(input.patch.branchPrefixCustom !== undefined && { branchPrefixCustom: input.patch.branchPrefixCustom, }), - ...(input.patch.hideImage !== undefined && { - hideImage: input.patch.hideImage, - }), - ...(input.patch.colorMode !== undefined && { - colorMode: input.patch.colorMode, - }), + ...(input.patch.hideImage !== undefined && { + hideImage: input.patch.hideImage, + }), + ...(input.patch.colorMode !== undefined && { + colorMode: input.patch.colorMode, + }), lastOpenedAt: Date.now(), }) .where(eq(projects.id, input.id)) 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 c33e4ccad8f..9b9f56a9fce 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts @@ -1,7 +1,9 @@ +import type { ProjectColorMode } from "@superset/local-db"; import { projects, workspaces, worktrees } from "@superset/local-db"; import { TRPCError } from "@trpc/server"; import { eq, isNotNull, isNull } from "drizzle-orm"; import { localDb } from "main/lib/local-db"; +import { PROJECT_COLOR_MODE_DEFAULT } from "shared/constants/project-colors"; import { z } from "zod"; import { publicProcedure, router } from "../../.."; import { getWorkspace } from "../utils/db-helpers"; @@ -152,16 +154,16 @@ export const createQueryProcedures = () => { const groupsMap = new Map< string, { - project: { - id: string; - name: string; - color: string; - colorMode: "border" | "background"; - tabOrder: number; - githubOwner: string | null; - mainRepoPath: string; - hideImage: boolean; - }; + project: { + id: string; + name: string; + color: string; + colorMode: ProjectColorMode; + tabOrder: number; + githubOwner: string | null; + mainRepoPath: string; + hideImage: boolean; + }; workspaces: Array<{ id: string; projectId: string; @@ -181,18 +183,18 @@ export const createQueryProcedures = () => { >(); for (const project of activeProjects) { - groupsMap.set(project.id, { - project: { - id: project.id, - name: project.name, - color: project.color, - colorMode: project.colorMode ?? "border", - // biome-ignore lint/style/noNonNullAssertion: filter guarantees tabOrder is not null - tabOrder: project.tabOrder!, - githubOwner: project.githubOwner ?? null, - mainRepoPath: project.mainRepoPath, - hideImage: project.hideImage ?? false, - }, + groupsMap.set(project.id, { + project: { + id: project.id, + name: project.name, + color: project.color, + colorMode: project.colorMode ?? PROJECT_COLOR_MODE_DEFAULT, + // biome-ignore lint/style/noNonNullAssertion: filter guarantees tabOrder is not null + tabOrder: project.tabOrder!, + githubOwner: project.githubOwner ?? null, + mainRepoPath: project.mainRepoPath, + hideImage: project.hideImage ?? false, + }, workspaces: [], }); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx index 372c63b0416..6a114d04cac 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx @@ -1,4 +1,4 @@ -import type { BranchPrefixMode } from "@superset/local-db"; +import type { BranchPrefixMode, ProjectColorMode } from "@superset/local-db"; import { Input } from "@superset/ui/input"; import { Label } from "@superset/ui/label"; import { @@ -14,9 +14,9 @@ import type { ReactNode } from "react"; import { useEffect, useState } from "react"; import { HiOutlineCog6Tooth, HiOutlinePaintBrush } from "react-icons/hi2"; import { electronTrpc } from "renderer/lib/electron-trpc"; -import type { ProjectColorMode } from "@superset/local-db"; import { PROJECT_COLOR_DEFAULT, + PROJECT_COLOR_MODE_DEFAULT, PROJECT_COLOR_MODE_LABELS, PROJECT_COLORS, } from "shared/constants/project-colors"; @@ -241,9 +241,7 @@ export function ProjectSettings({ projectId }: ProjectSettingsProps) { isDefault && "bg-muted", )} style={ - isDefault - ? undefined - : { backgroundColor: color.value } + isDefault ? undefined : { backgroundColor: color.value } } /> ); @@ -273,7 +271,7 @@ export function ProjectSettings({ projectId }: ProjectSettingsProps) {