diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index b31cfb6cc79..9b97fd754b8 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -1,4 +1,4 @@ -import { Component, createMemo, type JSX } from "solid-js" +import { Component, createMemo, Show, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { Button } from "@opencode-ai/ui/button" import { Select } from "@opencode-ai/ui/select" @@ -7,6 +7,7 @@ import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" import { showToast } from "@opencode-ai/ui/toast" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" +import { useServer } from "@/context/server" import { useSettings, monoFontFamily } from "@/context/settings" import { playSound, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" @@ -35,6 +36,7 @@ export const SettingsGeneral: Component = () => { const language = useLanguage() const platform = usePlatform() const settings = useSettings() + const server = useServer() const [store, setStore] = createStore({ checking: false, @@ -410,6 +412,27 @@ export const SettingsGeneral: Component = () => { + + {/* Desktop Section - Desktop only */} + + + {language.t("settings.general.section.desktop")} + + + + + server.dynamicSort.set(checked)} + /> + + + + + ) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index e2bf4498074..30871c0e2ac 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -35,6 +35,7 @@ import { bootstrapDirectory, bootstrapGlobal } from "./global-sync/bootstrap" import { sanitizeProject } from "./global-sync/utils" import type { ProjectMeta } from "./global-sync/types" import { SESSION_RECENT_LIMIT } from "./global-sync/types" +import { useServer } from "./server" type GlobalStore = { ready: boolean @@ -51,6 +52,7 @@ function createGlobalSync() { const globalSDK = useGlobalSDK() const platform = usePlatform() const language = useLanguage() + const server = useServer() const owner = getOwner() if (!owner) throw new Error("GlobalSync must be created within owner") @@ -280,6 +282,11 @@ function createGlobalSync() { .lsp.status() .then((x) => setStore("lsp", x.data ?? [])) }, + onSessionBusy: (sessionID) => { + if (!server.dynamicSort.enabled()) return + server.projects.bringToTop(directory) + setStore("session_active", sessionID, Date.now()) + }, }) }) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index 2feb7fe0884..d0091d3050a 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -171,6 +171,7 @@ export function createChildStoreManager(input: { session: [], sessionTotal: 0, session_status: {}, + session_active: {}, session_diff: {}, todo: {}, permission: {}, diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index c658d82c8b7..d536120c9a9 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -77,6 +77,7 @@ export function applyDirectoryEvent(input: { directory: string loadLsp: () => void vcsCache?: VcsCache + onSessionBusy?: (sessionID: string) => void }) { const event = input.event switch (event.type) { @@ -154,6 +155,7 @@ export function applyDirectoryEvent(input: { case "session.status": { const props = event.properties as { sessionID: string; status: SessionStatus } input.setStore("session_status", props.sessionID, reconcile(props.status)) + if (props.status.type === "busy") input.onSessionBusy?.(props.sessionID) break } case "message.updated": { diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index ade0b973a2a..0e90c4b6cfc 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -46,6 +46,9 @@ export type State = { session_status: { [sessionID: string]: SessionStatus } + session_active: { + [sessionID: string]: number + } session_diff: { [sessionID: string]: FileDiff[] } diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 72693e6ef64..096deea90fe 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -5,7 +5,7 @@ import { usePlatform } from "@/context/platform" import { Persist, persisted } from "@/utils/persist" import { checkServerHealth } from "@/utils/server-health" -type StoredProject = { worktree: string; expanded: boolean } +type StoredProject = { worktree: string; expanded: boolean; lastUsed?: number; pinned?: boolean } export function normalizeServerUrl(input: string) { const trimmed = input.trim() @@ -37,6 +37,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( list: [] as string[], projects: {} as Record, lastProject: {} as Record, + dynamicSort: true, }), ) @@ -202,6 +203,52 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( if (!key) return setStore("lastProject", key, directory) }, + bringToTop(directory: string) { + if (!store.dynamicSort) return + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index <= 0) return + if (current[index].pinned) return + const project = { ...current[index], lastUsed: Date.now() } + const rest = current.filter((_, i) => i !== index) + const insertAt = rest.findIndex((x) => !x.pinned) + const result = [...rest] + result.splice(insertAt === -1 ? rest.length : insertAt, 0, project) + setStore("projects", key, result) + }, + pin(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index === -1) return + const project = { ...current[index], pinned: true } + const rest = current.filter((_, i) => i !== index) + const lastPinned = rest.findLastIndex((x) => x.pinned) + const result = [...rest] + result.splice(lastPinned + 1, 0, project) + setStore("projects", key, result) + }, + unpin(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index === -1) return + setStore("projects", key, index, "pinned", false) + }, + isPinned(directory: string) { + const current = store.projects[origin()] ?? [] + return current.find((x) => x.worktree === directory)?.pinned ?? false + }, + }, + dynamicSort: { + enabled: createMemo(() => store.dynamicSort ?? true), + set(value: boolean) { + setStore("dynamicSort", value) + }, }, } }, diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 3778adcd679..30b99afa7da 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -494,6 +494,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "المشاريع والجلسات", "sidebar.settings": "الإعدادات", "sidebar.help": "مساعدة", + "sidebar.project.pin": "تثبيت في الأعلى", + "sidebar.project.unpin": "إلغاء التثبيت", "sidebar.workspaces.enable": "تمكين مساحات العمل", "sidebar.workspaces.disable": "تعطيل مساحات العمل", "sidebar.gettingStarted.title": "البدء", @@ -512,6 +514,10 @@ export const dict = { "settings.general.section.notifications": "إشعارات النظام", "settings.general.section.updates": "التحديثات", "settings.general.section.sounds": "المؤثرات الصوتية", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "ترتيب ديناميكي للشريط الجانبي", + "settings.general.desktop.dynamicSort.description": "نقل المشاريع النشطة مؤخراً إلى الأعلى", "settings.general.row.language.title": "اللغة", "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 74bfd8707c8..a924c5a25b6 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -498,6 +498,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projetos e sessões", "sidebar.settings": "Configurações", "sidebar.help": "Ajuda", + "sidebar.project.pin": "Fixar no topo", + "sidebar.project.unpin": "Desafixar", "sidebar.workspaces.enable": "Habilitar espaços de trabalho", "sidebar.workspaces.disable": "Desabilitar espaços de trabalho", "sidebar.gettingStarted.title": "Começando", @@ -516,6 +518,10 @@ export const dict = { "settings.general.section.notifications": "Notificações do sistema", "settings.general.section.updates": "Atualizações", "settings.general.section.sounds": "Efeitos sonoros", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Ordenação dinâmica da barra lateral", + "settings.general.desktop.dynamicSort.description": "Mover projetos recentemente ativos para o topo", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 7242fb5849f..f556e58d44d 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -498,6 +498,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projekter og sessioner", "sidebar.settings": "Indstillinger", "sidebar.help": "Hjælp", + "sidebar.project.pin": "Fastgør øverst", + "sidebar.project.unpin": "Frigør", "sidebar.workspaces.enable": "Aktiver arbejdsområder", "sidebar.workspaces.disable": "Deaktiver arbejdsområder", "sidebar.gettingStarted.title": "Kom i gang", @@ -516,6 +518,10 @@ export const dict = { "settings.general.section.notifications": "Systemmeddelelser", "settings.general.section.updates": "Opdateringer", "settings.general.section.sounds": "Lydeffekter", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamisk sidebjælkesortering", + "settings.general.desktop.dynamicSort.description": "Flyt nyligt aktive projekter til toppen", "settings.general.row.language.title": "Sprog", "settings.general.row.language.description": "Ændr visningssproget for OpenCode", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index bd8acae5e8f..3b153534ce2 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -541,6 +541,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projekte und Sitzungen", "sidebar.settings": "Einstellungen", "sidebar.help": "Hilfe", + "sidebar.project.pin": "Oben anheften", + "sidebar.project.unpin": "Loslösen", "sidebar.workspaces.enable": "Arbeitsbereiche aktivieren", "sidebar.workspaces.disable": "Arbeitsbereiche deaktivieren", "sidebar.gettingStarted.title": "Erste Schritte", @@ -560,6 +562,10 @@ export const dict = { "settings.general.section.notifications": "Systembenachrichtigungen", "settings.general.section.updates": "Updates", "settings.general.section.sounds": "Soundeffekte", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamische Seitenleisten-Sortierung", + "settings.general.desktop.dynamicSort.description": "Kürzlich aktive Projekte nach oben verschieben", "settings.general.row.language.title": "Sprache", "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 8fba6861b0b..0f609797444 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -569,6 +569,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projects and sessions", "sidebar.settings": "Settings", "sidebar.help": "Help", + "sidebar.project.pin": "Pin to top", + "sidebar.project.unpin": "Unpin", "sidebar.workspaces.enable": "Enable workspaces", "sidebar.workspaces.disable": "Disable workspaces", "sidebar.gettingStarted.title": "Getting started", @@ -588,6 +590,10 @@ export const dict = { "settings.general.section.notifications": "System notifications", "settings.general.section.updates": "Updates", "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Move recently active projects to the top", "settings.general.row.language.title": "Language", "settings.general.row.language.description": "Change the display language for OpenCode", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index f9b11ade870..747322bb172 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -501,6 +501,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Proyectos y sesiones", "sidebar.settings": "Ajustes", "sidebar.help": "Ayuda", + "sidebar.project.pin": "Fijar arriba", + "sidebar.project.unpin": "Desfijar", "sidebar.workspaces.enable": "Habilitar espacios de trabajo", "sidebar.workspaces.disable": "Deshabilitar espacios de trabajo", "sidebar.gettingStarted.title": "Empezando", @@ -519,6 +521,10 @@ export const dict = { "settings.general.section.notifications": "Notificaciones del sistema", "settings.general.section.updates": "Actualizaciones", "settings.general.section.sounds": "Efectos de sonido", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Ordenación dinámica de la barra lateral", + "settings.general.desktop.dynamicSort.description": "Mover proyectos recientemente activos a la parte superior", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 0cc81e5ea7a..599a8babd22 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -506,6 +506,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projets et sessions", "sidebar.settings": "Paramètres", "sidebar.help": "Aide", + "sidebar.project.pin": "Épingler en haut", + "sidebar.project.unpin": "Désépingler", "sidebar.workspaces.enable": "Activer les espaces de travail", "sidebar.workspaces.disable": "Désactiver les espaces de travail", "sidebar.gettingStarted.title": "Commencer", @@ -526,6 +528,10 @@ export const dict = { "settings.general.section.notifications": "Notifications système", "settings.general.section.updates": "Mises à jour", "settings.general.section.sounds": "Effets sonores", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Tri dynamique de la barre latérale", + "settings.general.desktop.dynamicSort.description": "Déplacer les projets récemment actifs vers le haut", "settings.general.row.language.title": "Langue", "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 337e1b0d349..b4970c2a376 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -493,6 +493,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "プロジェクトとセッション", "sidebar.settings": "設定", "sidebar.help": "ヘルプ", + "sidebar.project.pin": "上部に固定", + "sidebar.project.unpin": "固定を解除", "sidebar.workspaces.enable": "ワークスペースを有効化", "sidebar.workspaces.disable": "ワークスペースを無効化", "sidebar.gettingStarted.title": "はじめに", @@ -511,6 +513,10 @@ export const dict = { "settings.general.section.notifications": "システム通知", "settings.general.section.updates": "アップデート", "settings.general.section.sounds": "効果音", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "サイドバーの動的ソート", + "settings.general.desktop.dynamicSort.description": "最近アクティブなプロジェクトを上部に移動", "settings.general.row.language.title": "言語", "settings.general.row.language.description": "OpenCodeの表示言語を変更します", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 283bb6f3bdc..8c23f234f08 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -499,6 +499,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "프로젝트 및 세션", "sidebar.settings": "설정", "sidebar.help": "도움말", + "sidebar.project.pin": "상단에 고정", + "sidebar.project.unpin": "고정 해제", "sidebar.workspaces.enable": "작업 공간 활성화", "sidebar.workspaces.disable": "작업 공간 비활성화", "sidebar.gettingStarted.title": "시작하기", @@ -517,6 +519,10 @@ export const dict = { "settings.general.section.notifications": "시스템 알림", "settings.general.section.updates": "업데이트", "settings.general.section.sounds": "효과음", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "사이드바 동적 정렬", + "settings.general.desktop.dynamicSort.description": "최근 활성 프로젝트를 상단으로 이동", "settings.general.row.language.title": "언어", "settings.general.row.language.description": "OpenCode 표시 언어 변경", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index bbffd0083d1..96e1309cad6 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -501,6 +501,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Prosjekter og sesjoner", "sidebar.settings": "Innstillinger", "sidebar.help": "Hjelp", + "sidebar.project.pin": "Fest øverst", + "sidebar.project.unpin": "Løsne", "sidebar.workspaces.enable": "Aktiver arbeidsområder", "sidebar.workspaces.disable": "Deaktiver arbeidsområder", "sidebar.gettingStarted.title": "Kom i gang", @@ -519,6 +521,10 @@ export const dict = { "settings.general.section.notifications": "Systemvarsler", "settings.general.section.updates": "Oppdateringer", "settings.general.section.sounds": "Lydeffekter", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamisk sidefeltssortering", + "settings.general.desktop.dynamicSort.description": "Flytt nylig aktive prosjekter til toppen", "settings.general.row.language.title": "Språk", "settings.general.row.language.description": "Endre visningsspråket for OpenCode", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 2d36ca8c180..6202733b006 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -500,6 +500,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projekty i sesje", "sidebar.settings": "Ustawienia", "sidebar.help": "Pomoc", + "sidebar.project.pin": "Przypnij na górze", + "sidebar.project.unpin": "Odepnij", "sidebar.workspaces.enable": "Włącz przestrzenie robocze", "sidebar.workspaces.disable": "Wyłącz przestrzenie robocze", "sidebar.gettingStarted.title": "Pierwsze kroki", @@ -518,6 +520,10 @@ export const dict = { "settings.general.section.notifications": "Powiadomienia systemowe", "settings.general.section.updates": "Aktualizacje", "settings.general.section.sounds": "Efekty dźwiękowe", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamiczne sortowanie paska bocznego", + "settings.general.desktop.dynamicSort.description": "Przenieś ostatnio aktywne projekty na górę", "settings.general.row.language.title": "Język", "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 18b0ba5f47d..f0d12b22fce 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -502,6 +502,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Проекты и сессии", "sidebar.settings": "Настройки", "sidebar.help": "Помощь", + "sidebar.project.pin": "Закрепить наверху", + "sidebar.project.unpin": "Открепить", "sidebar.workspaces.enable": "Включить рабочие пространства", "sidebar.workspaces.disable": "Отключить рабочие пространства", "sidebar.gettingStarted.title": "Начало работы", @@ -521,6 +523,10 @@ export const dict = { "settings.general.section.notifications": "Системные уведомления", "settings.general.section.updates": "Обновления", "settings.general.section.sounds": "Звуковые эффекты", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Динамическая сортировка боковой панели", + "settings.general.desktop.dynamicSort.description": "Перемещать недавно активные проекты наверх", "settings.general.row.language.title": "Язык", "settings.general.row.language.description": "Изменить язык отображения OpenCode", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index d48a7cea665..8b9a3d0c442 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -501,6 +501,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "โปรเจกต์และเซสชัน", "sidebar.settings": "การตั้งค่า", "sidebar.help": "ช่วยเหลือ", + "sidebar.project.pin": "ปักหมุดไว้ด้านบน", + "sidebar.project.unpin": "เลิกปักหมุด", "sidebar.workspaces.enable": "เปิดใช้งานพื้นที่ทำงาน", "sidebar.workspaces.disable": "ปิดใช้งานพื้นที่ทำงาน", "sidebar.gettingStarted.title": "เริ่มต้นใช้งาน", @@ -520,6 +522,10 @@ export const dict = { "settings.general.section.notifications": "การแจ้งเตือนระบบ", "settings.general.section.updates": "การอัปเดต", "settings.general.section.sounds": "เสียงเอฟเฟกต์", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "การจัดเรียงแถบด้านข้างแบบไดนามิก", + "settings.general.desktop.dynamicSort.description": "ย้ายโปรเจกต์ที่ใช้งานล่าสุดไปด้านบน", "settings.general.row.language.title": "ภาษา", "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 070064d1c41..66565ceb48b 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -534,6 +534,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "项目和会话", "sidebar.settings": "设置", "sidebar.help": "帮助", + "sidebar.project.pin": "置顶", + "sidebar.project.unpin": "取消置顶", "sidebar.workspaces.enable": "启用工作区", "sidebar.workspaces.disable": "禁用工作区", "sidebar.gettingStarted.title": "入门", @@ -552,6 +554,10 @@ export const dict = { "settings.general.section.notifications": "系统通知", "settings.general.section.updates": "更新", "settings.general.section.sounds": "音效", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "动态侧边栏排序", + "settings.general.desktop.dynamicSort.description": "将最近活跃的项目移至顶部", "settings.general.row.language.title": "语言", "settings.general.row.language.description": "更改 OpenCode 的显示语言", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 39dcd92e276..97a6f9287b1 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -531,6 +531,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "專案與工作階段", "sidebar.settings": "設定", "sidebar.help": "說明", + "sidebar.project.pin": "置頂", + "sidebar.project.unpin": "取消置頂", "sidebar.workspaces.enable": "啟用工作區", "sidebar.workspaces.disable": "停用工作區", "sidebar.gettingStarted.title": "開始使用", @@ -549,6 +551,10 @@ export const dict = { "settings.general.section.notifications": "系統通知", "settings.general.section.updates": "更新", "settings.general.section.sounds": "音效", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "動態側邊欄排序", + "settings.general.desktop.dynamicSort.description": "將最近活躍的專案移至頂部", "settings.general.row.language.title": "語言", "settings.general.row.language.description": "變更 OpenCode 的顯示語言", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 59adef4694a..dcb3adc4df9 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -576,6 +576,7 @@ export default function Layout(props: ParentProps) { const project = currentProject() if (!project) return [] as Session[] const now = Date.now() + const dynamic = server.dynamicSort.enabled() if (workspaceSetting()) { const dirs = workspaceIds(project) const activeDir = currentDir() @@ -585,13 +586,13 @@ export default function Layout(props: ParentProps) { const active = dir === activeDir if (!expanded && !active) continue const [dirStore] = globalSync.child(dir, { bootstrap: true }) - const dirSessions = sortedRootSessions(dirStore, now) + const dirSessions = sortedRootSessions(dirStore, now, dynamic) result.push(...dirSessions) } return result } const [projectStore] = globalSync.child(project.worktree) - return sortedRootSessions(projectStore, now) + return sortedRootSessions(projectStore, now, dynamic) }) type PrefetchQueue = { @@ -1729,6 +1730,21 @@ export default function Layout(props: ParentProps) { showEditProjectDialog(p())}> {language.t("common.edit")} + { + server.projects.isPinned(p().worktree) + ? server.projects.unpin(p().worktree) + : server.projects.pin(p().worktree) + }} + > + + {server.projects.isPinned(p().worktree) + ? language.t("sidebar.project.unpin") + : language.t("sidebar.project.pin")} + + workspaceKey(session.directory) === workspaceKey(directory) && !session.parentID && !session.time?.archived -export const sortedRootSessions = (store: { session: Session[]; path: { directory: string } }, now: number) => - store.session.filter((session) => isRootVisibleSession(session, store.path.directory)).toSorted(sortSessions(now)) +export const sortedRootSessions = ( + store: { session: Session[]; path: { directory: string }; session_active?: { [sessionID: string]: number } }, + now: number, + dynamicSort?: boolean, +) => { + const filtered = store.session.filter((session) => isRootVisibleSession(session, store.path.directory)) + if (!dynamicSort) return filtered.toSorted(sortSessions(now)) + return filtered.toSorted((a, b) => { + const aActive = store.session_active?.[a.id] ?? 0 + const bActive = store.session_active?.[b.id] ?? 0 + if (aActive !== bActive) return bActive - aActive + return (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created) + }) +} export const childMapByParent = (sessions: Session[]) => { const map = new Map() diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index c91dc987d80..3248e66be32 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 { useServer } from "@/context/server" import { ProjectIcon, SessionItem, type SessionItemProps } from "./sidebar-items" import { childMapByParent, displayName, sortedRootSessions } from "./helpers" import { projectSelected, projectTileActive } from "./sidebar-project-helpers" @@ -58,6 +59,7 @@ export const SortableProject = (props: { }): JSX.Element => { const globalSync = useGlobalSync() const language = useLanguage() + const server = useServer() const sortable = createSortable(props.project.worktree) const selected = createMemo(() => projectSelected(props.ctx.currentDir(), props.project.worktree, props.project.sandboxes), @@ -95,11 +97,13 @@ export const SortableProject = (props: { } const projectStore = createMemo(() => globalSync.child(props.project.worktree, { bootstrap: false })[0]) - const projectSessions = createMemo(() => sortedRootSessions(projectStore(), Date.now()).slice(0, 2)) + const projectSessions = createMemo(() => + sortedRootSessions(projectStore(), Date.now(), server.dynamicSort.enabled()).slice(0, 2), + ) const projectChildren = createMemo(() => childMapByParent(projectStore().session)) const workspaceSessions = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) - return sortedRootSessions(data, Date.now()).slice(0, 2) + return sortedRootSessions(data, Date.now(), server.dynamicSort.enabled()).slice(0, 2) } const workspaceChildren = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) @@ -149,6 +153,21 @@ export const SortableProject = (props: { props.ctx.showEditProjectDialog(props.project)}> {language.t("common.edit")} + { + server.projects.isPinned(props.project.worktree) + ? server.projects.unpin(props.project.worktree) + : server.projects.pin(props.project.worktree) + }} + > + + {server.projects.isPinned(props.project.worktree) + ? language.t("sidebar.project.unpin") + : language.t("sidebar.project.pin")} + + base64Encode(props.directory)) - const sessions = createMemo(() => sortedRootSessions(workspaceStore, Date.now())) + const sessions = createMemo(() => sortedRootSessions(workspaceStore, Date.now(), server.dynamicSort.enabled())) const children = createMemo(() => childMapByParent(workspaceStore.session)) const local = createMemo(() => props.directory === props.project.worktree) const active = createMemo(() => props.ctx.currentDir() === props.directory) @@ -347,12 +349,13 @@ export const LocalWorkspace = (props: { }): JSX.Element => { const globalSync = useGlobalSync() const language = useLanguage() + const server = useServer() const workspace = createMemo(() => { const [store, setStore] = globalSync.child(props.project.worktree) return { store, setStore } }) const slug = createMemo(() => base64Encode(props.project.worktree)) - const sessions = createMemo(() => sortedRootSessions(workspace().store, Date.now())) + const sessions = createMemo(() => sortedRootSessions(workspace().store, Date.now(), server.dynamicSort.enabled())) const children = createMemo(() => childMapByParent(workspace().store.session)) const booted = createMemo((prev) => prev || workspace().store.status === "complete", false) const loading = createMemo(() => !booted() && sessions().length === 0)