diff options
| author | Adam <[email protected]> | 2026-02-14 19:33:22 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-14 19:33:22 -0600 |
| commit | 85b5f5b705e8f7852184a4ef147bdc826639d224 (patch) | |
| tree | 0b0ca4bf1e8115363e854774e01be44b0b5ea937 /packages | |
| parent | 460a87f359cef2cdcd4638ba49b1d7d652ddedd5 (diff) | |
| download | opencode-85b5f5b705e8f7852184a4ef147bdc826639d224.tar.gz opencode-85b5f5b705e8f7852184a4ef147bdc826639d224.zip | |
feat(app): clear notifications action (#13668)
Co-authored-by: adamelmore <[email protected]>
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/app/e2e/selectors.ts | 3 | ||||
| -rw-r--r-- | packages/app/src/i18n/ar.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/br.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/bs.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/da.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/de.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/en.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/es.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/fr.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/ja.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/ko.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/no.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/pl.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/ru.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/th.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/zh.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/i18n/zht.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 17 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-project.tsx | 155 |
19 files changed, 126 insertions, 65 deletions
diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 52c9007ea..1a0afbab1 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 e3792a3c3..81cc92bf6 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 07d6ce467..9ed3a9fc6 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 7d10da6ed..206aae372 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 ac5c4d494..6bf67168f 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 99a950631..4b6b43a57 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 99513edaa..fd70f389e 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 7a6c4974e..135a63fef 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 fc3bf2667..1ab0c72d5 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 b597db02a..6f092a60f 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 525bd0356..4d814d43d 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 98e79e189..63bc66acf 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 983c9c14a..2a3ea7bfb 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 f2c87fe0f..93e5b2742 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 689e82118..3b3486b5c 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 1b40013b6..6489b7025 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 34aec01b9..a01b76c05 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 7eb064f42..7d4a5c0cb 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")} </DropdownMenu.ItemLabel> </DropdownMenu.Item> + <DropdownMenu.Item + data-action="project-clear-notifications" + data-project={base64Encode(p().worktree)} + disabled={unseenCount() === 0} + onSelect={clearNotifications} + > + <DropdownMenu.ItemLabel> + {language.t("sidebar.project.clearNotifications")} + </DropdownMenu.ItemLabel> + </DropdownMenu.Item> <DropdownMenu.Separator /> <DropdownMenu.Item data-action="project-close-menu" diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index 28b129bf7..e19e6f430 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -10,6 +10,7 @@ import { createSortable } from "@thisbeyond/solid-dnd" import { type LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" +import { useNotification } from "@/context/notification" import { ProjectIcon, SessionItem, type SessionItemProps } from "./sidebar-items" import { childMapByParent, displayName, sortedRootSessions } from "./helpers" import { projectSelected, projectTileActive } from "./sidebar-project-helpers" @@ -59,6 +60,7 @@ const ProjectTile = (props: { selected: Accessor<boolean> active: Accessor<boolean> overlay: Accessor<boolean> + dirs: Accessor<string[]> 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<typeof useLanguage> -}): JSX.Element => ( - <ContextMenu - modal={!props.sidebarHovering()} - onOpenChange={(value) => { - props.setMenu(value) - if (value) props.setOpen(false) - }} - > - <ContextMenu.Trigger - as="button" - type="button" - aria-label={displayName(props.project)} - data-action="project-switch" - data-project={base64Encode(props.project.worktree)} - classList={{ - "flex items-center justify-center size-10 p-1 rounded-lg overflow-hidden transition-colors cursor-default": true, - "bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover": props.selected(), - "bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base": - !props.selected() && !props.active(), - "bg-surface-base-hover border border-border-weak-base": !props.selected() && props.active(), - }} - onMouseEnter={(event: MouseEvent) => { - 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 ( + <ContextMenu + modal={!props.sidebarHovering()} + onOpenChange={(value) => { + props.setMenu(value) + if (value) props.setOpen(false) }} - onClick={() => props.navigateToProject(props.project.worktree)} - onBlur={() => props.setOpen(false)} > - <ProjectIcon project={props.project} notify /> - </ContextMenu.Trigger> - <ContextMenu.Portal mount={!props.mobile ? props.nav() : undefined}> - <ContextMenu.Content> - <ContextMenu.Item onSelect={() => props.showEditProjectDialog(props.project)}> - <ContextMenu.ItemLabel>{props.language.t("common.edit")}</ContextMenu.ItemLabel> - </ContextMenu.Item> - <ContextMenu.Item - data-action="project-workspaces-toggle" - data-project={base64Encode(props.project.worktree)} - disabled={props.project.vcs !== "git" && !props.workspacesEnabled(props.project)} - onSelect={() => props.toggleProjectWorkspaces(props.project)} - > - <ContextMenu.ItemLabel> - {props.workspacesEnabled(props.project) - ? props.language.t("sidebar.workspaces.disable") - : props.language.t("sidebar.workspaces.enable")} - </ContextMenu.ItemLabel> - </ContextMenu.Item> - <ContextMenu.Separator /> - <ContextMenu.Item - data-action="project-close-menu" - data-project={base64Encode(props.project.worktree)} - onSelect={() => props.closeProject(props.project.worktree)} - > - <ContextMenu.ItemLabel>{props.language.t("common.close")}</ContextMenu.ItemLabel> - </ContextMenu.Item> - </ContextMenu.Content> - </ContextMenu.Portal> - </ContextMenu> -) + <ContextMenu.Trigger + as="button" + type="button" + aria-label={displayName(props.project)} + data-action="project-switch" + data-project={base64Encode(props.project.worktree)} + classList={{ + "flex items-center justify-center size-10 p-1 rounded-lg overflow-hidden transition-colors cursor-default": true, + "bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover": props.selected(), + "bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base": + !props.selected() && !props.active(), + "bg-surface-base-hover border border-border-weak-base": !props.selected() && props.active(), + }} + onMouseEnter={(event: MouseEvent) => { + 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)} + > + <ProjectIcon project={props.project} notify /> + </ContextMenu.Trigger> + <ContextMenu.Portal mount={!props.mobile ? props.nav() : undefined}> + <ContextMenu.Content> + <ContextMenu.Item onSelect={() => props.showEditProjectDialog(props.project)}> + <ContextMenu.ItemLabel>{props.language.t("common.edit")}</ContextMenu.ItemLabel> + </ContextMenu.Item> + <ContextMenu.Item + data-action="project-workspaces-toggle" + data-project={base64Encode(props.project.worktree)} + disabled={props.project.vcs !== "git" && !props.workspacesEnabled(props.project)} + onSelect={() => props.toggleProjectWorkspaces(props.project)} + > + <ContextMenu.ItemLabel> + {props.workspacesEnabled(props.project) + ? props.language.t("sidebar.workspaces.disable") + : props.language.t("sidebar.workspaces.enable")} + </ContextMenu.ItemLabel> + </ContextMenu.Item> + <ContextMenu.Item + data-action="project-clear-notifications" + data-project={base64Encode(props.project.worktree)} + disabled={unseenCount() === 0} + onSelect={clear} + > + <ContextMenu.ItemLabel>{props.language.t("sidebar.project.clearNotifications")}</ContextMenu.ItemLabel> + </ContextMenu.Item> + <ContextMenu.Separator /> + <ContextMenu.Item + data-action="project-close-menu" + data-project={base64Encode(props.project.worktree)} + onSelect={() => props.closeProject(props.project.worktree)} + > + <ContextMenu.ItemLabel>{props.language.t("common.close")}</ContextMenu.ItemLabel> + </ContextMenu.Item> + </ContextMenu.Content> + </ContextMenu.Portal> + </ContextMenu> + ) +} 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} |
