diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 52c9007ea19..1a0afbab102 100644 --- a/packages/app/e2e/selectors.ts +++ b/packages/app/e2e/selectors.ts @@ -30,6 +30,9 @@ export const projectMenuTriggerSelector = (slug: string) => export const projectCloseMenuSelector = (slug: string) => `[data-action="project-close-menu"][data-project="${slug}"]` +export const projectClearNotificationsSelector = (slug: string) => + `[data-action="project-clear-notifications"][data-project="${slug}"]` + export const projectWorkspacesToggleSelector = (slug: string) => `[data-action="project-workspaces-toggle"][data-project="${slug}"]` diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index e3792a3c3cc..81cc92bf6de 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -509,6 +509,7 @@ export const dict = { "sidebar.gettingStarted.line2": "قم بتوصيل أي موفر لاستخدام النماذج، بما في ذلك Claude و GPT و Gemini وما إلى ذلك.", "sidebar.project.recentSessions": "الجلسات الحديثة", "sidebar.project.viewAllSessions": "عرض جميع الجلسات", + "sidebar.project.clearNotifications": "مسح الإشعارات", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "سطح المكتب", "settings.section.server": "الخادم", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 07d6ce467ae..9ed3a9fc6fc 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -515,6 +515,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Conecte qualquer provedor para usar modelos, incluindo Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Sessões recentes", "sidebar.project.viewAllSessions": "Ver todas as sessões", + "sidebar.project.clearNotifications": "Limpar notificações", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Servidor", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 7d10da6ed87..206aae3729d 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -576,6 +576,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Poveži bilo kojeg provajdera da koristiš modele, npr. Claude, GPT, Gemini itd.", "sidebar.project.recentSessions": "Nedavne sesije", "sidebar.project.viewAllSessions": "Prikaži sve sesije", + "sidebar.project.clearNotifications": "Očisti obavijesti", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index ac5c4d494b1..6bf67168fb0 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -572,6 +572,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Forbind enhver udbyder for at bruge modeller, inkl. Claude, GPT, Gemini osv.", "sidebar.project.recentSessions": "Seneste sessioner", "sidebar.project.viewAllSessions": "Vis alle sessioner", + "sidebar.project.clearNotifications": "Ryd notifikationer", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 99a95063107..4b6b43a57c0 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -524,6 +524,7 @@ export const dict = { "Verbinden Sie einen beliebigen Anbieter, um Modelle wie Claude, GPT, Gemini usw. zu nutzen.", "sidebar.project.recentSessions": "Letzte Sitzungen", "sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen", + "sidebar.project.clearNotifications": "Benachrichtigungen löschen", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 99513edaa17..fd70f389ecf 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -577,6 +577,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Connect any provider to use models, inc. Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Recent sessions", "sidebar.project.viewAllSessions": "View all sessions", + "sidebar.project.clearNotifications": "Clear notifications", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 7a6c4974e09..135a63fef74 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -579,6 +579,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Conecta cualquier proveedor para usar modelos, inc. Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Sesiones recientes", "sidebar.project.viewAllSessions": "Ver todas las sesiones", + "sidebar.project.clearNotifications": "Borrar notificaciones", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index fc3bf266794..1ab0c72d53a 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -523,6 +523,7 @@ export const dict = { "Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Sessions récentes", "sidebar.project.viewAllSessions": "Voir toutes les sessions", + "sidebar.project.clearNotifications": "Effacer les notifications", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Bureau", "settings.section.server": "Serveur", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index b597db02a58..6f092a60f68 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -513,6 +513,7 @@ export const dict = { "sidebar.gettingStarted.line2": "プロバイダーを接続して、Claude、GPT、Geminiなどのモデルを使用できます。", "sidebar.project.recentSessions": "最近のセッション", "sidebar.project.viewAllSessions": "すべてのセッションを表示", + "sidebar.project.clearNotifications": "通知をクリア", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "デスクトップ", "settings.section.server": "サーバー", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 525bd035651..4d814d43d0a 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -514,6 +514,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Claude, GPT, Gemini 등을 포함한 모델을 사용하려면 공급자를 연결하세요.", "sidebar.project.recentSessions": "최근 세션", "sidebar.project.viewAllSessions": "모든 세션 보기", + "sidebar.project.clearNotifications": "알림 지우기", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "데스크톱", "settings.section.server": "서버", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 98e79e1896a..63bc66acfcd 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -579,6 +579,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Koble til en leverandør for å bruke modeller, inkl. Claude, GPT, Gemini osv.", "sidebar.project.recentSessions": "Nylige sesjoner", "sidebar.project.viewAllSessions": "Vis alle sesjoner", + "sidebar.project.clearNotifications": "Fjern varsler", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 983c9c14ac1..2a3ea7bfb18 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -514,6 +514,7 @@ export const dict = { "sidebar.gettingStarted.line2": "Połącz dowolnego dostawcę, aby używać modeli, w tym Claude, GPT, Gemini itp.", "sidebar.project.recentSessions": "Ostatnie sesje", "sidebar.project.viewAllSessions": "Zobacz wszystkie sesje", + "sidebar.project.clearNotifications": "Wyczyść powiadomienia", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Pulpit", "settings.section.server": "Serwer", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index f2c87fe0f1e..93e5b274253 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -578,6 +578,7 @@ export const dict = { "Подключите любого провайдера для использования моделей, включая Claude, GPT, Gemini и др.", "sidebar.project.recentSessions": "Недавние сессии", "sidebar.project.viewAllSessions": "Посмотреть все сессии", + "sidebar.project.clearNotifications": "Очистить уведомления", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Приложение", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 689e8211896..3b3486b5c70 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -571,6 +571,7 @@ export const dict = { "sidebar.gettingStarted.line2": "เชื่อมต่อผู้ให้บริการใด ๆ เพื่อใช้โมเดล รวมถึง Claude, GPT, Gemini ฯลฯ", "sidebar.project.recentSessions": "เซสชันล่าสุด", "sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด", + "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 1b40013b60d..6489b702542 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -569,6 +569,7 @@ export const dict = { "sidebar.gettingStarted.line2": "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。", "sidebar.project.recentSessions": "最近会话", "sidebar.project.viewAllSessions": "查看全部会话", + "sidebar.project.clearNotifications": "清除通知", "app.name.desktop": "OpenCode Desktop", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 34aec01b9cb..a01b76c0521 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -567,6 +567,7 @@ export const dict = { "sidebar.gettingStarted.line2": "連線任意提供者即可使用更多模型,如 Claude、GPT、Gemini 等。", "sidebar.project.recentSessions": "最近工作階段", "sidebar.project.viewAllSessions": "查看全部工作階段", + "sidebar.project.clearNotifications": "清除通知", "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "桌面", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 7eb064f425d..7d4a5c0cb81 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1692,6 +1692,13 @@ export default function Layout(props: ParentProps) { }) const projectId = createMemo(() => panelProps.project?.id ?? "") const workspaces = createMemo(() => workspaceIds(panelProps.project)) + const unseenCount = createMemo(() => + workspaces().reduce((total, directory) => total + notification.project.unseenCount(directory), 0), + ) + const clearNotifications = () => + workspaces() + .filter((directory) => notification.project.unseenCount(directory) > 0) + .forEach((directory) => notification.project.markViewed(directory)) const workspacesEnabled = createMemo(() => { const project = panelProps.project if (!project) return false @@ -1769,6 +1776,16 @@ export default function Layout(props: ParentProps) { : language.t("sidebar.workspaces.enable")} + + + {language.t("sidebar.project.clearNotifications")} + + active: Accessor overlay: Accessor + dirs: Accessor onProjectMouseEnter: (worktree: string, event: MouseEvent) => void onProjectMouseLeave: (worktree: string) => void onProjectFocus: (worktree: string) => void @@ -70,73 +72,94 @@ const ProjectTile = (props: { setMenu: (value: boolean) => void setOpen: (value: boolean) => void language: ReturnType -}): JSX.Element => ( - { - props.setMenu(value) - if (value) props.setOpen(false) - }} - > - { - if (!props.overlay()) return - props.onProjectMouseEnter(props.project.worktree, event) - }} - onMouseLeave={() => { - if (!props.overlay()) return - props.onProjectMouseLeave(props.project.worktree) - }} - onFocus={() => { - if (!props.overlay()) return - props.onProjectFocus(props.project.worktree) +}): JSX.Element => { + const notification = useNotification() + const unseenCount = createMemo(() => + props.dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0), + ) + + const clear = () => + props + .dirs() + .filter((directory) => notification.project.unseenCount(directory) > 0) + .forEach((directory) => notification.project.markViewed(directory)) + + return ( + { + props.setMenu(value) + if (value) props.setOpen(false) }} - onClick={() => props.navigateToProject(props.project.worktree)} - onBlur={() => props.setOpen(false)} > - - - - - props.showEditProjectDialog(props.project)}> - {props.language.t("common.edit")} - - props.toggleProjectWorkspaces(props.project)} - > - - {props.workspacesEnabled(props.project) - ? props.language.t("sidebar.workspaces.disable") - : props.language.t("sidebar.workspaces.enable")} - - - - props.closeProject(props.project.worktree)} - > - {props.language.t("common.close")} - - - - -) + { + if (!props.overlay()) return + props.onProjectMouseEnter(props.project.worktree, event) + }} + onMouseLeave={() => { + if (!props.overlay()) return + props.onProjectMouseLeave(props.project.worktree) + }} + onFocus={() => { + if (!props.overlay()) return + props.onProjectFocus(props.project.worktree) + }} + onClick={() => props.navigateToProject(props.project.worktree)} + onBlur={() => props.setOpen(false)} + > + + + + + props.showEditProjectDialog(props.project)}> + {props.language.t("common.edit")} + + props.toggleProjectWorkspaces(props.project)} + > + + {props.workspacesEnabled(props.project) + ? props.language.t("sidebar.workspaces.disable") + : props.language.t("sidebar.workspaces.enable")} + + + + {props.language.t("sidebar.project.clearNotifications")} + + + props.closeProject(props.project.worktree)} + > + {props.language.t("common.close")} + + + + + ) +} const ProjectPreviewPanel = (props: { project: LocalProject @@ -254,6 +277,7 @@ export const SortableProject = (props: { ) const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2)) const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project)) + const dirs = createMemo(() => props.ctx.workspaceIds(props.project)) const [open, setOpen] = createSignal(false) const [menu, setMenu] = createSignal(false) @@ -304,6 +328,7 @@ export const SortableProject = (props: { selected={selected} active={active} overlay={overlay} + dirs={dirs} onProjectMouseEnter={props.ctx.onProjectMouseEnter} onProjectMouseLeave={props.ctx.onProjectMouseLeave} onProjectFocus={props.ctx.onProjectFocus}