mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-07-10 00:14:27 +00:00
fix(ui): return to active projects from home (#411)
## Summary - Return to an existing open project when users choose that from the already-open folder dialog. - When users click a recent folder that is already open, ask whether to switch to the open project or intentionally open another instance. - Surface a prominent Return to Active Project action so leaving the home screen no longer depends on the subtle top-right close affordance. - Keep multi-instance workflows available without requiring users to remember a separate row-level button. Fixes #281 ## Why Users can accidentally open the same workspace several times from the add-project screen. The app now detects that ambiguity when the already-open folder is clicked and asks what should happen, instead of making the user remember a special alternate button ahead of time. The dialog keeps the copy short: the title states that the project is already open, and the buttons carry the actual choices. ## Verification - `git diff --check` - `npm run typecheck --workspace @codenomad/ui` - `npm run build --workspace @codenomad/ui` - `npm run build:tauri`
This commit is contained in:
parent
562c6325c7
commit
a3231c3da5
10 changed files with 175 additions and 10 deletions
|
|
@ -34,6 +34,7 @@ import {
|
|||
import { useConfig } from "./stores/preferences"
|
||||
import {
|
||||
createInstance,
|
||||
getExistingInstanceForFolder,
|
||||
instances,
|
||||
stopInstance,
|
||||
disconnectedInstance,
|
||||
|
|
@ -95,6 +96,11 @@ const App: Component = () => {
|
|||
const [escapeInDebounce, setEscapeInDebounce] = createSignal(false)
|
||||
const [instanceTabBarHeight, setInstanceTabBarHeight] = createSignal(0)
|
||||
const [sidecarPickerOpen, setSidecarPickerOpen] = createSignal(false)
|
||||
const [alreadyOpenFolderChoice, setAlreadyOpenFolderChoice] = createSignal<{
|
||||
folderPath: string
|
||||
binaryPath: string
|
||||
instanceId: string
|
||||
} | null>(null)
|
||||
|
||||
const phoneQuery = useMediaQuery("(max-width: 767px)")
|
||||
const isPhoneLayout = createMemo(() => phoneQuery())
|
||||
|
|
@ -258,15 +264,24 @@ const App: Component = () => {
|
|||
|
||||
const launchErrorMessage = () => launchError()?.message ?? ""
|
||||
|
||||
async function handleSelectFolder(folderPath: string, binaryPath?: string) {
|
||||
async function handleSelectFolder(folderPath: string, binaryPath?: string, options?: { forceNew?: boolean }) {
|
||||
if (!folderPath) {
|
||||
return
|
||||
}
|
||||
setIsSelectingFolder(true)
|
||||
const selectedBinary = binaryPath || serverSettings().opencodeBinary || "opencode"
|
||||
recordWorkspaceLaunch(folderPath, selectedBinary)
|
||||
clearLaunchError()
|
||||
|
||||
if (!options?.forceNew) {
|
||||
const existingInstance = getExistingInstanceForFolder(folderPath)
|
||||
if (existingInstance) {
|
||||
setAlreadyOpenFolderChoice({ folderPath, binaryPath: selectedBinary, instanceId: existingInstance.id })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setIsSelectingFolder(true)
|
||||
try {
|
||||
recordWorkspaceLaunch(folderPath, selectedBinary)
|
||||
clearLaunchError()
|
||||
const instanceId = await createInstance(folderPath, selectedBinary)
|
||||
selectInstanceTab(instanceId)
|
||||
setShowFolderSelection(false)
|
||||
|
|
@ -285,6 +300,26 @@ const App: Component = () => {
|
|||
}
|
||||
}
|
||||
|
||||
function dismissAlreadyOpenFolderChoice() {
|
||||
setAlreadyOpenFolderChoice(null)
|
||||
}
|
||||
|
||||
function switchToAlreadyOpenFolder() {
|
||||
const choice = alreadyOpenFolderChoice()
|
||||
if (!choice) return
|
||||
setAlreadyOpenFolderChoice(null)
|
||||
selectInstanceTab(choice.instanceId)
|
||||
setShowFolderSelection(false)
|
||||
log.info("Selected existing instance", { instanceId: choice.instanceId, folderPath: choice.folderPath })
|
||||
}
|
||||
|
||||
function openAnotherFolderInstance() {
|
||||
const choice = alreadyOpenFolderChoice()
|
||||
if (!choice) return
|
||||
setAlreadyOpenFolderChoice(null)
|
||||
void handleSelectFolder(choice.folderPath, choice.binaryPath, { forceNew: true })
|
||||
}
|
||||
|
||||
function handleLaunchErrorClose() {
|
||||
clearLaunchError()
|
||||
}
|
||||
|
|
@ -609,6 +644,7 @@ const App: Component = () => {
|
|||
onSelectFolder={handleSelectFolder}
|
||||
isLoading={isSelectingFolder()}
|
||||
onOpenSidecar={handleOpenSidecarPicker}
|
||||
activeProjectLabel={activeInstance()?.folder ?? null}
|
||||
onClose={() => {
|
||||
setShowFolderSelection(false)
|
||||
clearLaunchError()
|
||||
|
|
@ -620,6 +656,30 @@ const App: Component = () => {
|
|||
|
||||
<SettingsScreen />
|
||||
<SideCarPickerDialog open={sidecarPickerOpen()} onClose={() => setSidecarPickerOpen(false)} onOpenSidecar={handleOpenSidecar} />
|
||||
<Show when={alreadyOpenFolderChoice()}>
|
||||
<Dialog open modal onOpenChange={(open) => !open && dismissAlreadyOpenFolderChoice()}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay class="modal-overlay z-[60]" />
|
||||
<Dialog.Content class="modal-surface fixed left-1/2 top-1/2 z-[1310] w-full max-w-sm -translate-x-1/2 -translate-y-1/2 p-6 border border-base shadow-2xl" tabIndex={-1}>
|
||||
<Dialog.Title class="text-lg font-semibold text-primary">
|
||||
{t("folderSelection.recent.alreadyOpenTitle")}
|
||||
</Dialog.Title>
|
||||
<Dialog.Description class="text-sm text-secondary mt-1">
|
||||
{t("folderSelection.recent.alreadyOpenMessage")}
|
||||
</Dialog.Description>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button type="button" class="button-secondary" onClick={openAnotherFolderInstance}>
|
||||
{t("folderSelection.recent.openAnotherInstance")}
|
||||
</button>
|
||||
<button type="button" class="button-primary" onClick={switchToAlreadyOpenFolder}>
|
||||
{t("folderSelection.recent.switchToOpenProject")}
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
</Show>
|
||||
|
||||
<AlertDialog />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Dialog } from "@kobalte/core/dialog"
|
||||
import { Select } from "@kobalte/core/select"
|
||||
import { Component, createSignal, Show, For, onMount, onCleanup, createEffect } from "solid-js"
|
||||
import { Folder, Clock, Trash2, FolderPlus, Settings, ChevronRight, MonitorUp, Star, Languages, ChevronDown, X, Globe, Loader2, GitBranch } from "lucide-solid"
|
||||
import { Component, createMemo, createSignal, Show, For, onMount, onCleanup, createEffect } from "solid-js"
|
||||
import { Folder, Clock, Trash2, FolderPlus, Settings, ChevronRight, MonitorUp, Star, Languages, ChevronDown, X, Globe, Loader2, GitBranch, ArrowLeft } from "lucide-solid"
|
||||
import { useConfig } from "../stores/preferences"
|
||||
import DirectoryBrowserDialog from "./directory-browser-dialog"
|
||||
import Kbd from "./kbd"
|
||||
|
|
@ -18,6 +18,7 @@ import { openExternalUrl } from "../lib/external-url"
|
|||
import { serverApi } from "../lib/api-client"
|
||||
import { canOpenRemoteWindows, isTauriHost } from "../lib/runtime-env"
|
||||
import { openRemoteServerWindow } from "../lib/native/remote-window"
|
||||
import { getExistingInstanceForFolder } from "../stores/instances"
|
||||
|
||||
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
||||
const GITHUB_URL = "https://github.com/NeuralNomadsAI/CodeNomad"
|
||||
|
|
@ -27,10 +28,11 @@ type HomeTab = "local" | "servers"
|
|||
|
||||
|
||||
interface FolderSelectionViewProps {
|
||||
onSelectFolder: (folder: string, binaryPath?: string) => void
|
||||
onSelectFolder: (folder: string, binaryPath?: string, options?: { forceNew?: boolean }) => void
|
||||
onOpenSidecar?: () => void
|
||||
isLoading?: boolean
|
||||
onClose?: () => void
|
||||
activeProjectLabel?: string | null
|
||||
}
|
||||
|
||||
const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
||||
|
|
@ -85,6 +87,11 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||
const serverList = () => remoteServers()
|
||||
const isLoading = () => Boolean(props.isLoading)
|
||||
const canUseRemoteServerWindows = () => canOpenRemoteWindows()
|
||||
const activeProjectName = createMemo(() => {
|
||||
const label = props.activeProjectLabel?.trim()
|
||||
if (!label) return null
|
||||
return splitFolderPath(label).baseName
|
||||
})
|
||||
|
||||
function getActiveListLength() {
|
||||
return activeTab() === "local" ? folders().length : serverList().length
|
||||
|
|
@ -862,8 +869,10 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||
ref={(el) => (recentListRef = el)}
|
||||
>
|
||||
<For each={folders()}>
|
||||
{(folder, index) => (
|
||||
<div
|
||||
{(folder, index) => {
|
||||
const existingInstance = () => getExistingInstanceForFolder(folder.path)
|
||||
|
||||
return <div
|
||||
class="panel-list-item"
|
||||
classList={{
|
||||
"panel-list-item-highlight": focusMode() === "recent" && selectedIndex() === index(),
|
||||
|
|
@ -889,6 +898,11 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||
<span class="text-sm font-medium truncate text-primary">
|
||||
{splitFolderPath(folder.path).baseName}
|
||||
</span>
|
||||
<Show when={existingInstance()}>
|
||||
<span class="rounded-full border border-base px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-secondary">
|
||||
{t("folderSelection.recent.openBadge")}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pl-6 text-xs text-muted min-w-0">
|
||||
<span class="font-mono truncate-start flex-1 min-w-0">
|
||||
|
|
@ -912,7 +926,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
@ -947,6 +961,23 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||
<Kbd shortcut="cmd+n" class="ml-2 kbd-hint" />
|
||||
</button>
|
||||
|
||||
<Show when={props.onClose}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.onClose?.()}
|
||||
class="button-primary w-full flex items-center justify-center text-sm"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<ArrowLeft class="w-4 h-4" />
|
||||
<span>{t("folderSelection.actions.returnToProject")}</span>
|
||||
</div>
|
||||
</button>
|
||||
</Show>
|
||||
|
||||
<Show when={activeProjectName()}>
|
||||
{(name) => <p class="text-xs text-muted text-center">{t("folderSelection.actions.returnToProjectSubtitle", { name: name() })}</p>}
|
||||
</Show>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={openCloneDialog}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.recent.subtitle.one": "{count} folder available",
|
||||
"folderSelection.recent.subtitle.other": "{count} folders available",
|
||||
"folderSelection.recent.remove": "Remove from recent",
|
||||
"folderSelection.recent.openBadge": "Open",
|
||||
"folderSelection.recent.alreadyOpenTitle": "Project already open",
|
||||
"folderSelection.recent.alreadyOpenMessage": "Choose how to open this folder.",
|
||||
"folderSelection.recent.switchToOpenProject": "Switch to open project",
|
||||
"folderSelection.recent.openAnotherInstance": "Open another instance",
|
||||
|
||||
"folderSelection.browse.title": "Browse for Folder",
|
||||
"folderSelection.browse.subtitle": "Select any folder on your computer",
|
||||
|
|
@ -38,6 +43,8 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.actions.title": "Open Folder or Connect Server",
|
||||
"folderSelection.actions.subtitle": "Open local folder or connect to a CodeNomad server",
|
||||
"folderSelection.actions.connectButton": "Connect CodeNomad Server",
|
||||
"folderSelection.actions.returnToProject": "Return to Active Project",
|
||||
"folderSelection.actions.returnToProjectSubtitle": "Go back to {name}",
|
||||
|
||||
"folderSelection.advancedSettings": "Advanced Settings",
|
||||
"folderSelection.opencode": "OpenCode",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.recent.subtitle.one": "{count} carpeta disponible",
|
||||
"folderSelection.recent.subtitle.other": "{count} carpetas disponibles",
|
||||
"folderSelection.recent.remove": "Eliminar de recientes",
|
||||
"folderSelection.recent.openBadge": "Abierta",
|
||||
"folderSelection.recent.alreadyOpenTitle": "Proyecto ya abierto",
|
||||
"folderSelection.recent.alreadyOpenMessage": "Elige cómo abrir esta carpeta.",
|
||||
"folderSelection.recent.switchToOpenProject": "Cambiar al proyecto abierto",
|
||||
"folderSelection.recent.openAnotherInstance": "Abrir otra instancia",
|
||||
|
||||
"folderSelection.browse.title": "Buscar carpeta",
|
||||
"folderSelection.browse.subtitle": "Selecciona cualquier carpeta en tu ordenador",
|
||||
|
|
@ -38,6 +43,8 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.actions.title": "Abrir carpeta o conectar servidor",
|
||||
"folderSelection.actions.subtitle": "Abre una carpeta local o conéctate a un servidor de CodeNomad",
|
||||
"folderSelection.actions.connectButton": "Conectar servidor CodeNomad",
|
||||
"folderSelection.actions.returnToProject": "Volver al proyecto activo",
|
||||
"folderSelection.actions.returnToProjectSubtitle": "Volver a {name}",
|
||||
|
||||
"folderSelection.advancedSettings": "Configuración avanzada",
|
||||
"folderSelection.opencode": "OpenCode",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.recent.subtitle.one": "{count} dossier disponible",
|
||||
"folderSelection.recent.subtitle.other": "{count} dossiers disponibles",
|
||||
"folderSelection.recent.remove": "Retirer des récents",
|
||||
"folderSelection.recent.openBadge": "Ouvert",
|
||||
"folderSelection.recent.alreadyOpenTitle": "Projet déjà ouvert",
|
||||
"folderSelection.recent.alreadyOpenMessage": "Choisissez comment ouvrir ce dossier.",
|
||||
"folderSelection.recent.switchToOpenProject": "Basculer vers le projet ouvert",
|
||||
"folderSelection.recent.openAnotherInstance": "Ouvrir une autre instance",
|
||||
|
||||
"folderSelection.browse.title": "Parcourir un dossier",
|
||||
"folderSelection.browse.subtitle": "Sélectionnez n'importe quel dossier sur votre ordinateur",
|
||||
|
|
@ -38,6 +43,8 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.actions.title": "Ouvrir un dossier ou se connecter à un serveur",
|
||||
"folderSelection.actions.subtitle": "Ouvrez un dossier local ou connectez-vous à un serveur CodeNomad",
|
||||
"folderSelection.actions.connectButton": "Se connecter au serveur CodeNomad",
|
||||
"folderSelection.actions.returnToProject": "Revenir au projet actif",
|
||||
"folderSelection.actions.returnToProjectSubtitle": "Retourner vers {name}",
|
||||
|
||||
"folderSelection.advancedSettings": "Paramètres avancés",
|
||||
"folderSelection.opencode": "OpenCode",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.recent.subtitle.one": "תיקייה אחת זמינה",
|
||||
"folderSelection.recent.subtitle.other": "{count} תיקיות זמינות",
|
||||
"folderSelection.recent.remove": "הסר מהרשימה האחרונה",
|
||||
"folderSelection.recent.openBadge": "פתוח",
|
||||
"folderSelection.recent.alreadyOpenTitle": "הפרויקט כבר פתוח",
|
||||
"folderSelection.recent.alreadyOpenMessage": "בחר כיצד לפתוח את התיקייה הזו.",
|
||||
"folderSelection.recent.switchToOpenProject": "עבור לפרויקט הפתוח",
|
||||
"folderSelection.recent.openAnotherInstance": "פתח מופע נוסף",
|
||||
|
||||
"folderSelection.browse.title": "עיון בתיקייה",
|
||||
"folderSelection.browse.subtitle": "בחר כל תיקייה במחשב שלך",
|
||||
|
|
@ -38,6 +43,8 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.actions.title": "פתח תיקייה או התחבר לשרת",
|
||||
"folderSelection.actions.subtitle": "פתח תיקייה מקומית או התחבר לשרת CodeNomad",
|
||||
"folderSelection.actions.connectButton": "התחבר לשרת CodeNomad",
|
||||
"folderSelection.actions.returnToProject": "חזור לפרויקט הפעיל",
|
||||
"folderSelection.actions.returnToProjectSubtitle": "חזור אל {name}",
|
||||
|
||||
"folderSelection.advancedSettings": "הגדרות מתקדמות",
|
||||
"folderSelection.opencode": "OpenCode",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.recent.subtitle.one": "{count} 件のフォルダ",
|
||||
"folderSelection.recent.subtitle.other": "{count} 件のフォルダ",
|
||||
"folderSelection.recent.remove": "履歴から削除",
|
||||
"folderSelection.recent.openBadge": "開いています",
|
||||
"folderSelection.recent.alreadyOpenTitle": "プロジェクトはすでに開いています",
|
||||
"folderSelection.recent.alreadyOpenMessage": "このフォルダの開き方を選択してください。",
|
||||
"folderSelection.recent.switchToOpenProject": "開いているプロジェクトに切り替え",
|
||||
"folderSelection.recent.openAnotherInstance": "別のインスタンスを開く",
|
||||
|
||||
"folderSelection.browse.title": "フォルダを参照",
|
||||
"folderSelection.browse.subtitle": "コンピュータ上の任意のフォルダを選択",
|
||||
|
|
@ -38,6 +43,8 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.actions.title": "フォルダを開くかサーバーに接続",
|
||||
"folderSelection.actions.subtitle": "ローカルフォルダを開くか CodeNomad サーバーに接続します",
|
||||
"folderSelection.actions.connectButton": "CodeNomad サーバーに接続",
|
||||
"folderSelection.actions.returnToProject": "アクティブなプロジェクトに戻る",
|
||||
"folderSelection.actions.returnToProjectSubtitle": "{name} に戻る",
|
||||
|
||||
"folderSelection.advancedSettings": "詳細設定",
|
||||
"folderSelection.opencode": "OpenCode",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.recent.subtitle.one": "Доступна {count} папка",
|
||||
"folderSelection.recent.subtitle.other": "Доступно {count} папок",
|
||||
"folderSelection.recent.remove": "Убрать из недавних",
|
||||
"folderSelection.recent.openBadge": "Открыта",
|
||||
"folderSelection.recent.alreadyOpenTitle": "Проект уже открыт",
|
||||
"folderSelection.recent.alreadyOpenMessage": "Выберите, как открыть эту папку.",
|
||||
"folderSelection.recent.switchToOpenProject": "Перейти к открытому проекту",
|
||||
"folderSelection.recent.openAnotherInstance": "Открыть еще одну инстанцию",
|
||||
|
||||
"folderSelection.browse.title": "Выбрать папку",
|
||||
"folderSelection.browse.subtitle": "Выберите любую папку на компьютере",
|
||||
|
|
@ -38,6 +43,8 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.actions.title": "Открыть папку или подключить сервер",
|
||||
"folderSelection.actions.subtitle": "Откройте локальную папку или подключитесь к серверу CodeNomad",
|
||||
"folderSelection.actions.connectButton": "Подключить сервер CodeNomad",
|
||||
"folderSelection.actions.returnToProject": "Вернуться к активному проекту",
|
||||
"folderSelection.actions.returnToProjectSubtitle": "Вернуться к {name}",
|
||||
|
||||
"folderSelection.advancedSettings": "Расширенные настройки",
|
||||
"folderSelection.opencode": "OpenCode",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.recent.subtitle.one": "{count} 个文件夹可用",
|
||||
"folderSelection.recent.subtitle.other": "{count} 个文件夹可用",
|
||||
"folderSelection.recent.remove": "从最近列表移除",
|
||||
"folderSelection.recent.openBadge": "已打开",
|
||||
"folderSelection.recent.alreadyOpenTitle": "项目已打开",
|
||||
"folderSelection.recent.alreadyOpenMessage": "选择如何打开此文件夹。",
|
||||
"folderSelection.recent.switchToOpenProject": "切换到已打开的项目",
|
||||
"folderSelection.recent.openAnotherInstance": "打开另一个实例",
|
||||
|
||||
"folderSelection.browse.title": "浏览文件夹",
|
||||
"folderSelection.browse.subtitle": "选择你电脑上的任意文件夹",
|
||||
|
|
@ -38,6 +43,8 @@ export const folderSelectionMessages = {
|
|||
"folderSelection.actions.title": "打开文件夹或连接服务器",
|
||||
"folderSelection.actions.subtitle": "打开本地文件夹或连接到 CodeNomad 服务器",
|
||||
"folderSelection.actions.connectButton": "连接 CodeNomad 服务器",
|
||||
"folderSelection.actions.returnToProject": "返回当前项目",
|
||||
"folderSelection.actions.returnToProjectSubtitle": "返回到 {name}",
|
||||
|
||||
"folderSelection.advancedSettings": "高级设置",
|
||||
"folderSelection.opencode": "OpenCode",
|
||||
|
|
|
|||
|
|
@ -553,6 +553,30 @@ async function createInstance(folder: string, _binaryPath?: string): Promise<str
|
|||
}
|
||||
}
|
||||
|
||||
function normalizeInstanceFolderPath(folder: string): string {
|
||||
const trimmed = folder.replace(/[\\/]+$/, "")
|
||||
const windowsLike = /^(?:[A-Za-z]:[\\/]|[\\/]{2})/.test(trimmed)
|
||||
if (!windowsLike) {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
return trimmed.replace(/\\/g, "/").toLowerCase()
|
||||
}
|
||||
|
||||
function getExistingInstanceForFolder(folder: string): Instance | null {
|
||||
if (!folder) return null
|
||||
const target = normalizeInstanceFolderPath(folder)
|
||||
const matches = Array.from(instances().values()).filter((instance) => {
|
||||
if (instance.status === "stopped") return false
|
||||
return normalizeInstanceFolderPath(instance.folder) === target
|
||||
})
|
||||
|
||||
if (matches.length === 0) return null
|
||||
|
||||
const activeId = activeInstanceId()
|
||||
return matches.find((instance) => instance.id === activeId) ?? matches.find((instance) => instance.status === "ready") ?? matches[0] ?? null
|
||||
}
|
||||
|
||||
async function stopInstance(id: string) {
|
||||
const instance = instances().get(id)
|
||||
if (!instance) return
|
||||
|
|
@ -1169,6 +1193,7 @@ export {
|
|||
updateInstance,
|
||||
removeInstance,
|
||||
createInstance,
|
||||
getExistingInstanceForFolder,
|
||||
stopInstance,
|
||||
getActiveInstance,
|
||||
addLog,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue