初始化web服务功能
This commit is contained in:
@@ -8,8 +8,7 @@ import {
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react"
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event"
|
||||
import { revealItemInDir } from "@tauri-apps/plugin-opener"
|
||||
import { revealItemInDir, subscribe } from "@/lib/platform"
|
||||
import ignore from "ignore"
|
||||
import { Check, ChevronRight } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
@@ -36,8 +35,7 @@ import {
|
||||
saveFileCopy,
|
||||
startFileTreeWatch,
|
||||
stopFileTreeWatch,
|
||||
} from "@/lib/tauri"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
} from "@/lib/api"
|
||||
import {
|
||||
emitAttachFileToSession,
|
||||
emitAppendTextToSession,
|
||||
@@ -1907,7 +1905,7 @@ export function FileTreeTab() {
|
||||
const rootPath = folder?.path
|
||||
if (!rootPath) return
|
||||
|
||||
let unlisten: UnlistenFn | null = null
|
||||
let unlisten: (() => void) | null = null
|
||||
const normalizedRootPath = normalizeComparePath(rootPath)
|
||||
|
||||
const scheduleTreeRefresh = (refreshGitStatus: boolean) => {
|
||||
@@ -2046,20 +2044,17 @@ export function FileTreeTab() {
|
||||
}
|
||||
|
||||
try {
|
||||
unlisten = await listen<FileTreeChangedEvent>(
|
||||
unlisten = await subscribe<FileTreeChangedEvent>(
|
||||
"folder://file-tree-changed",
|
||||
(event) => {
|
||||
(payload) => {
|
||||
if (
|
||||
normalizeComparePath(event.payload.root_path) !==
|
||||
normalizedRootPath
|
||||
normalizeComparePath(payload.root_path) !== normalizedRootPath
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const changedPaths =
|
||||
event.payload.changed_paths.map(normalizeComparePath)
|
||||
const shouldRefreshGitStatus =
|
||||
event.payload.refresh_git_status ?? true
|
||||
const changedPaths = payload.changed_paths.map(normalizeComparePath)
|
||||
const shouldRefreshGitStatus = payload.refresh_git_status ?? true
|
||||
const nonGitChangedPaths = changedPaths.filter(
|
||||
(path) => !isGitMetadataPath(path)
|
||||
)
|
||||
@@ -2069,13 +2064,13 @@ export function FileTreeTab() {
|
||||
(path) => !filePathSetRef.current.has(path)
|
||||
)
|
||||
const needsTreeRefresh =
|
||||
event.payload.full_reload ||
|
||||
payload.full_reload ||
|
||||
(!onlyGitMetadataChanges &&
|
||||
(event.payload.kind !== "modify" ||
|
||||
(payload.kind !== "modify" ||
|
||||
nonGitChangedPaths.length === 0 ||
|
||||
hasUnknownPath))
|
||||
|
||||
if (onlyGitMetadataChanges && !event.payload.full_reload) {
|
||||
if (onlyGitMetadataChanges && !payload.full_reload) {
|
||||
if (shouldRefreshGitStatus) {
|
||||
scheduleStatusRefresh()
|
||||
}
|
||||
@@ -2085,13 +2080,13 @@ export function FileTreeTab() {
|
||||
scheduleStatusRefresh()
|
||||
}
|
||||
|
||||
if (onlyGitMetadataChanges && !event.payload.full_reload) {
|
||||
if (onlyGitMetadataChanges && !payload.full_reload) {
|
||||
return
|
||||
}
|
||||
|
||||
const changedActivePath = getActiveChangedFilePath(
|
||||
nonGitChangedPaths,
|
||||
event.payload.full_reload
|
||||
payload.full_reload
|
||||
)
|
||||
if (!changedActivePath) return
|
||||
|
||||
@@ -2145,7 +2140,7 @@ export function FileTreeTab() {
|
||||
pendingTreeRefreshRef.current = false
|
||||
pendingTreeRefreshNeedsStatusRef.current = false
|
||||
pendingStatusRefreshRef.current = false
|
||||
disposeTauriListener(unlisten, "AuxPanelFileTree.fileTreeChanged")
|
||||
unlisten?.()
|
||||
void stopFileTreeWatch(rootPath)
|
||||
}
|
||||
}, [fetchTree, folder?.path, openFilePreview, t])
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event"
|
||||
import { subscribe } from "@/lib/platform"
|
||||
import { ChevronsDownUp, ChevronsUpDown } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { toast } from "sonner"
|
||||
@@ -46,8 +46,7 @@ import {
|
||||
openCommitWindow,
|
||||
startFileTreeWatch,
|
||||
stopFileTreeWatch,
|
||||
} from "@/lib/tauri"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
} from "@/lib/api"
|
||||
import type { FileTreeChangedEvent, GitStatusEntry } from "@/lib/types"
|
||||
import {
|
||||
AlertDialog,
|
||||
@@ -611,7 +610,7 @@ export function GitChangesTab() {
|
||||
const rootPath = folder?.path
|
||||
if (!rootPath || !isChangesTabActive) return
|
||||
|
||||
let unlisten: UnlistenFn | null = null
|
||||
let unlisten: (() => void) | null = null
|
||||
const normalizedRootPath = normalizeComparePath(rootPath)
|
||||
|
||||
const scheduleRefresh = () => {
|
||||
@@ -631,16 +630,15 @@ export function GitChangesTab() {
|
||||
}
|
||||
|
||||
try {
|
||||
unlisten = await listen<FileTreeChangedEvent>(
|
||||
unlisten = await subscribe<FileTreeChangedEvent>(
|
||||
"folder://file-tree-changed",
|
||||
(event) => {
|
||||
(payload) => {
|
||||
if (
|
||||
normalizeComparePath(event.payload.root_path) !==
|
||||
normalizedRootPath
|
||||
normalizeComparePath(payload.root_path) !== normalizedRootPath
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (!shouldRefreshFromEvent(event.payload)) return
|
||||
if (!shouldRefreshFromEvent(payload)) return
|
||||
scheduleRefresh()
|
||||
}
|
||||
)
|
||||
@@ -656,7 +654,7 @@ export function GitChangesTab() {
|
||||
clearTimeout(refreshTimerRef.current)
|
||||
refreshTimerRef.current = null
|
||||
}
|
||||
disposeTauriListener(unlisten, "AuxPanelGitChanges.fileTreeChanged")
|
||||
unlisten?.()
|
||||
void stopFileTreeWatch(rootPath)
|
||||
}
|
||||
}, [fetchChanges, folder?.path, isChangesTabActive])
|
||||
|
||||
@@ -75,8 +75,7 @@ import {
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
import { subscribe } from "@/lib/platform"
|
||||
import { useFolderContext } from "@/contexts/folder-context"
|
||||
import { useWorkspaceContext } from "@/contexts/workspace-context"
|
||||
import {
|
||||
@@ -86,7 +85,7 @@ import {
|
||||
gitLog,
|
||||
gitNewBranch,
|
||||
openPushWindow,
|
||||
} from "@/lib/tauri"
|
||||
} from "@/lib/api"
|
||||
import type { GitBranchList, GitLogEntry, GitLogFileChange } from "@/lib/types"
|
||||
import { toast } from "sonner"
|
||||
import { toErrorMessage } from "@/lib/app-error"
|
||||
@@ -874,11 +873,11 @@ export function GitLogTab() {
|
||||
"folder://git-push-succeeded",
|
||||
] as const
|
||||
|
||||
const unlistens: (UnlistenFn | null)[] = events.map(() => null)
|
||||
const unlistens: ((() => void) | null)[] = events.map(() => null)
|
||||
|
||||
events.forEach((eventName, i) => {
|
||||
listen<{ folder_id: number }>(eventName, (event) => {
|
||||
if (event.payload.folder_id !== folder.id) return
|
||||
subscribe<{ folder_id: number }>(eventName, (payload) => {
|
||||
if (payload.folder_id !== folder.id) return
|
||||
void refreshBranches()
|
||||
void fetchLog({ inline: true })
|
||||
})
|
||||
@@ -891,8 +890,8 @@ export function GitLogTab() {
|
||||
})
|
||||
|
||||
return () => {
|
||||
events.forEach((eventName, i) => {
|
||||
disposeTauriListener(unlistens[i], `GitLogTab.${eventName}`)
|
||||
events.forEach((_eventName, i) => {
|
||||
unlistens[i]?.()
|
||||
})
|
||||
}
|
||||
}, [folder, refreshBranches, fetchLog])
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useRef, useCallback, useMemo, useEffect } from "react"
|
||||
import { emit, listen, type UnlistenFn } from "@tauri-apps/api/event"
|
||||
const emitEvent = async (event: string, payload?: unknown) => {
|
||||
try {
|
||||
const { emit } = await import("@tauri-apps/api/event")
|
||||
await emit(event, payload)
|
||||
} catch { /* not in Tauri */ }
|
||||
}
|
||||
import { openFileDialog, subscribe } from "@/lib/platform"
|
||||
import {
|
||||
GitBranch,
|
||||
ChevronDown,
|
||||
@@ -62,7 +68,6 @@ import { useTranslations } from "next-intl"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { open } from "@tauri-apps/plugin-dialog"
|
||||
import {
|
||||
gitInit,
|
||||
gitPull,
|
||||
@@ -79,11 +84,10 @@ import {
|
||||
setFolderParentBranch,
|
||||
openStashWindow,
|
||||
openPushWindow,
|
||||
} from "@/lib/tauri"
|
||||
} from "@/lib/api"
|
||||
import { RemoteManageDialog } from "@/components/layout/remote-manage-dialog"
|
||||
import { ConflictDialog } from "@/components/layout/conflict-dialog"
|
||||
import { StashDialog } from "@/components/layout/stash-dialog"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
import { toErrorMessage } from "@/lib/app-error"
|
||||
import type { GitBranchList, GitConflictInfo } from "@/lib/types"
|
||||
import { toast } from "sonner"
|
||||
@@ -167,15 +171,15 @@ export function BranchDropdown({
|
||||
useEffect(() => {
|
||||
if (!folder) return
|
||||
|
||||
let unlisten: UnlistenFn | null = null
|
||||
let unlisten: (() => void) | null = null
|
||||
|
||||
listen<GitCommitSucceededEventPayload>(
|
||||
subscribe<GitCommitSucceededEventPayload>(
|
||||
"folder://git-commit-succeeded",
|
||||
(event) => {
|
||||
if (event.payload.folder_id !== folder.id) return
|
||||
(payload) => {
|
||||
if (payload.folder_id !== folder.id) return
|
||||
toast.success(t("toasts.commitCodeCompleted"), {
|
||||
description: t("toasts.committedFiles", {
|
||||
count: event.payload.committed_files,
|
||||
count: payload.committed_files,
|
||||
}),
|
||||
})
|
||||
onBranchChange()
|
||||
@@ -189,20 +193,20 @@ export function BranchDropdown({
|
||||
})
|
||||
|
||||
return () => {
|
||||
disposeTauriListener(unlisten, "BranchDropdown.gitCommitSucceeded")
|
||||
unlisten?.()
|
||||
}
|
||||
}, [folder, onBranchChange, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (!folder) return
|
||||
|
||||
let unlisten: UnlistenFn | null = null
|
||||
let unlisten: (() => void) | null = null
|
||||
|
||||
listen<GitPushSucceededEventPayload>(
|
||||
subscribe<GitPushSucceededEventPayload>(
|
||||
"folder://git-push-succeeded",
|
||||
(event) => {
|
||||
if (event.payload.folder_id !== folder.id) return
|
||||
const { pushed_commits, upstream_set } = event.payload
|
||||
(payload) => {
|
||||
if (payload.folder_id !== folder.id) return
|
||||
const { pushed_commits, upstream_set } = payload
|
||||
let description: string
|
||||
if (upstream_set) {
|
||||
description =
|
||||
@@ -226,7 +230,7 @@ export function BranchDropdown({
|
||||
})
|
||||
|
||||
return () => {
|
||||
disposeTauriListener(unlisten, "BranchDropdown.gitPushSucceeded")
|
||||
unlisten?.()
|
||||
}
|
||||
}, [folder, onBranchChange, t])
|
||||
|
||||
@@ -245,7 +249,7 @@ export function BranchDropdown({
|
||||
const successDescription = getSuccessDescription?.(result)
|
||||
updateTask(taskId, { status: "completed" })
|
||||
onBranchChange()
|
||||
void emit("folder://git-branch-changed", {
|
||||
void emitEvent("folder://git-branch-changed", {
|
||||
folder_id: folder?.id,
|
||||
})
|
||||
if (successDescription !== false) {
|
||||
@@ -326,9 +330,11 @@ export function BranchDropdown({
|
||||
}
|
||||
|
||||
async function handleBrowseWorktreePath() {
|
||||
const selected = await open({ directory: true, multiple: false })
|
||||
const selected = await openFileDialog({ directory: true, multiple: false })
|
||||
if (selected) {
|
||||
setWorktreePath(selected)
|
||||
setWorktreePath(
|
||||
Array.isArray(selected) ? selected[0] : selected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event"
|
||||
import { subscribe } from "@/lib/platform"
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react"
|
||||
import { ChevronDown, Play, Plus, Square } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
@@ -19,8 +19,7 @@ import {
|
||||
listFolderCommands,
|
||||
terminalKill,
|
||||
terminalList,
|
||||
} from "@/lib/tauri"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
} from "@/lib/api"
|
||||
import type { FolderCommand, TerminalEvent } from "@/lib/types"
|
||||
import { CommandManageDialog } from "./command-manage-dialog"
|
||||
|
||||
@@ -54,7 +53,7 @@ export function CommandDropdown() {
|
||||
const [runningCommandTerminals, setRunningCommandTerminals] = useState<
|
||||
Record<number, string>
|
||||
>({})
|
||||
const exitUnlistenersRef = useRef<Map<string, UnlistenFn>>(new Map())
|
||||
const exitUnlistenersRef = useRef<Map<string, () => void>>(new Map())
|
||||
const runningCommandTerminalsRef = useRef<Record<number, string>>({})
|
||||
|
||||
const folderId = folder?.id ?? 0
|
||||
@@ -67,7 +66,7 @@ export function CommandDropdown() {
|
||||
const clearRunningByTerminalId = useCallback((terminalId: string) => {
|
||||
const unlisten = exitUnlistenersRef.current.get(terminalId)
|
||||
if (unlisten) {
|
||||
disposeTauriListener(unlisten, "CommandDropdown.terminalExit")
|
||||
unlisten()
|
||||
exitUnlistenersRef.current.delete(terminalId)
|
||||
}
|
||||
|
||||
@@ -86,7 +85,7 @@ export function CommandDropdown() {
|
||||
|
||||
const clearAllRunningStates = useCallback(() => {
|
||||
for (const unlisten of exitUnlistenersRef.current.values()) {
|
||||
disposeTauriListener(unlisten, "CommandDropdown.terminalExit")
|
||||
unlisten()
|
||||
}
|
||||
exitUnlistenersRef.current.clear()
|
||||
setRunningCommandTerminals({})
|
||||
@@ -165,7 +164,7 @@ export function CommandDropdown() {
|
||||
async (terminalId: string) => {
|
||||
if (exitUnlistenersRef.current.has(terminalId)) return
|
||||
try {
|
||||
const unlisten = await listen<TerminalEvent>(
|
||||
const unlisten = await subscribe<TerminalEvent>(
|
||||
`terminal://exit/${terminalId}`,
|
||||
() => {
|
||||
clearRunningByTerminalId(terminalId)
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
createFolderCommand,
|
||||
updateFolderCommand,
|
||||
deleteFolderCommand,
|
||||
} from "@/lib/tauri"
|
||||
} from "@/lib/api"
|
||||
|
||||
interface CommandDraft {
|
||||
id: number | null
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
gitStatus,
|
||||
deleteFileTreeEntry,
|
||||
readFilePreview,
|
||||
} from "@/lib/tauri"
|
||||
} from "@/lib/api"
|
||||
import type { GitStatusEntry } from "@/lib/types"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { toast } from "sonner"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event"
|
||||
import { subscribe } from "@/lib/platform"
|
||||
import { AlertTriangle, Check, FileWarning, Loader2 } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { toast } from "sonner"
|
||||
@@ -20,8 +20,7 @@ import {
|
||||
gitAbortOperation,
|
||||
gitContinueOperation,
|
||||
openMergeWindow,
|
||||
} from "@/lib/tauri"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
} from "@/lib/api"
|
||||
import type { GitConflictInfo } from "@/lib/types"
|
||||
|
||||
interface ConflictDialogProps {
|
||||
@@ -76,15 +75,15 @@ export function ConflictDialog({
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
|
||||
let unlistenResolved: UnlistenFn | null = null
|
||||
let unlistenCompleted: UnlistenFn | null = null
|
||||
let unlistenAborted: UnlistenFn | null = null
|
||||
let unlistenResolved: (() => void) | null = null
|
||||
let unlistenCompleted: (() => void) | null = null
|
||||
let unlistenAborted: (() => void) | null = null
|
||||
|
||||
listen<{ folder_id: number; file: string }>(
|
||||
subscribe<{ folder_id: number; file: string }>(
|
||||
"folder://merge-conflict-resolved",
|
||||
(event) => {
|
||||
if (event.payload.folder_id !== folderId) return
|
||||
setResolvedFiles((prev) => new Set([...prev, event.payload.file]))
|
||||
(payload) => {
|
||||
if (payload.folder_id !== folderId) return
|
||||
setResolvedFiles((prev) => new Set([...prev, payload.file]))
|
||||
}
|
||||
)
|
||||
.then((fn) => {
|
||||
@@ -92,8 +91,8 @@ export function ConflictDialog({
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
listen<{ folder_id: number }>("folder://merge-completed", (event) => {
|
||||
if (event.payload.folder_id !== folderId) return
|
||||
subscribe<{ folder_id: number }>("folder://merge-completed", (payload) => {
|
||||
if (payload.folder_id !== folderId) return
|
||||
setDone(true)
|
||||
onResolved()
|
||||
onClose()
|
||||
@@ -105,8 +104,8 @@ export function ConflictDialog({
|
||||
|
||||
// Merge was aborted (user clicked abort in merge window, or window closed)
|
||||
// Reset resolved state since abort reverts all changes
|
||||
listen<{ folder_id: number }>("folder://merge-aborted", (event) => {
|
||||
if (event.payload.folder_id !== folderId) return
|
||||
subscribe<{ folder_id: number }>("folder://merge-aborted", (payload) => {
|
||||
if (payload.folder_id !== folderId) return
|
||||
setDone(true)
|
||||
setResolvedFiles(new Set())
|
||||
onClose()
|
||||
@@ -117,12 +116,9 @@ export function ConflictDialog({
|
||||
.catch(() => {})
|
||||
|
||||
return () => {
|
||||
disposeTauriListener(
|
||||
unlistenResolved,
|
||||
"ConflictDialog.mergeConflictResolved"
|
||||
)
|
||||
disposeTauriListener(unlistenCompleted, "ConflictDialog.mergeCompleted")
|
||||
disposeTauriListener(unlistenAborted, "ConflictDialog.mergeAborted")
|
||||
unlistenResolved?.()
|
||||
unlistenCompleted?.()
|
||||
unlistenAborted?.()
|
||||
}
|
||||
}, [open, folderId, onResolved, onClose])
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState } from "react"
|
||||
import { ChevronDown, Folder, FolderOpen, GitBranch } from "lucide-react"
|
||||
import { open } from "@tauri-apps/plugin-dialog"
|
||||
import { useTranslations } from "next-intl"
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -17,7 +16,8 @@ import {
|
||||
listOpenFolders,
|
||||
loadFolderHistory,
|
||||
openFolderWindow,
|
||||
} from "@/lib/tauri"
|
||||
} from "@/lib/api"
|
||||
import { openFileDialog } from "@/lib/platform"
|
||||
import { useFolderContext } from "@/contexts/folder-context"
|
||||
import { CloneDialog } from "@/components/welcome/clone-dialog"
|
||||
import type { FolderHistoryEntry } from "@/lib/types"
|
||||
@@ -50,9 +50,11 @@ export function FolderNameDropdown() {
|
||||
}
|
||||
|
||||
async function handleOpenFolder() {
|
||||
const selected = await open({ directory: true, multiple: false })
|
||||
const selected = await openFileDialog({ directory: true, multiple: false })
|
||||
if (selected) {
|
||||
await openFolderWindow(selected)
|
||||
await openFolderWindow(
|
||||
Array.isArray(selected) ? selected[0] : selected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
useState,
|
||||
type KeyboardEvent as ReactKeyboardEvent,
|
||||
} from "react"
|
||||
import { open } from "@tauri-apps/plugin-dialog"
|
||||
import {
|
||||
Columns2,
|
||||
FileCode2,
|
||||
@@ -19,7 +18,8 @@ import {
|
||||
Settings,
|
||||
} from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { getGitBranch, openFolderWindow, openSettingsWindow } from "@/lib/tauri"
|
||||
import { getGitBranch, openFolderWindow, openSettingsWindow } from "@/lib/api"
|
||||
import { openFileDialog } from "@/lib/platform"
|
||||
import { useFolderContext } from "@/contexts/folder-context"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useSidebarContext } from "@/contexts/sidebar-context"
|
||||
@@ -79,8 +79,9 @@ export function FolderTitleBar() {
|
||||
|
||||
const handleOpenFolder = useCallback(async () => {
|
||||
try {
|
||||
const selected = await open({ directory: true, multiple: false })
|
||||
if (!selected) return
|
||||
const result = await openFileDialog({ directory: true, multiple: false })
|
||||
if (!result) return
|
||||
const selected = Array.isArray(result) ? result[0] : result
|
||||
await openFolderWindow(selected)
|
||||
} catch (err) {
|
||||
console.error("[FolderTitleBar] failed to open folder:", err)
|
||||
|
||||
@@ -51,7 +51,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { gitLog, gitPush, gitPushInfo, gitShowFile } from "@/lib/tauri"
|
||||
import { gitLog, gitPush, gitPushInfo, gitShowFile } from "@/lib/api"
|
||||
import { toErrorMessage } from "@/lib/app-error"
|
||||
import { languageFromPath } from "@/lib/language-detect"
|
||||
import type { GitLogEntry, GitLogFileChange, GitPushInfo } from "@/lib/types"
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
gitAddRemote,
|
||||
gitRemoveRemote,
|
||||
gitSetRemoteUrl,
|
||||
} from "@/lib/tauri"
|
||||
} from "@/lib/api"
|
||||
|
||||
interface RemoteDraft {
|
||||
originalName: string | null
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { gitStashPush } from "@/lib/tauri"
|
||||
import { gitStashPush } from "@/lib/api"
|
||||
import { toErrorMessage } from "@/lib/app-error"
|
||||
|
||||
interface StashDialogProps {
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
import { useAcpActions } from "@/contexts/acp-connections-context"
|
||||
import { openUrl } from "@tauri-apps/plugin-opener"
|
||||
import { openSettingsWindow } from "@/lib/tauri"
|
||||
import { openUrl } from "@/lib/platform"
|
||||
import { openSettingsWindow } from "@/lib/api"
|
||||
import { AGENT_LABELS, type AgentType } from "@/lib/types"
|
||||
|
||||
const KNOWN_AGENT_TYPES = new Set<AgentType>(
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
gitStashApply,
|
||||
gitStashDrop,
|
||||
gitShowFile,
|
||||
} from "@/lib/tauri"
|
||||
} from "@/lib/api"
|
||||
import { toErrorMessage } from "@/lib/app-error"
|
||||
import { languageFromPath } from "@/lib/language-detect"
|
||||
import type { GitStashEntry, GitStatusEntry } from "@/lib/types"
|
||||
|
||||
@@ -1,72 +1,74 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { isDesktop } from "@/lib/platform"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { usePlatform } from "@/hooks/use-platform"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
async function getTauriWindow() {
|
||||
const { getCurrentWindow } = await import("@tauri-apps/api/window")
|
||||
return getCurrentWindow()
|
||||
}
|
||||
|
||||
export function WindowControls() {
|
||||
const t = useTranslations("Folder.windowControls")
|
||||
const { isWindows } = usePlatform()
|
||||
const [isMaximized, setIsMaximized] = useState(false)
|
||||
const appWindowRef = useRef<Awaited<
|
||||
ReturnType<typeof getTauriWindow>
|
||||
> | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWindows) return
|
||||
if (!isWindows || !isDesktop()) return
|
||||
|
||||
let disposed = false
|
||||
let unlistenResize: (() => void) | null = null
|
||||
let resizeFrame: number | null = null
|
||||
const appWindow = getCurrentWindow()
|
||||
|
||||
const syncMaximized = async () => {
|
||||
try {
|
||||
const maximized = await appWindow.isMaximized()
|
||||
if (!disposed) {
|
||||
setIsMaximized(maximized)
|
||||
}
|
||||
} catch {
|
||||
if (!disposed) {
|
||||
setIsMaximized(false)
|
||||
getTauriWindow().then((appWindow) => {
|
||||
if (disposed) return
|
||||
appWindowRef.current = appWindow
|
||||
|
||||
const syncMaximized = async () => {
|
||||
try {
|
||||
const maximized = await appWindow.isMaximized()
|
||||
if (!disposed) setIsMaximized(maximized)
|
||||
} catch {
|
||||
if (!disposed) setIsMaximized(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const scheduleSync = () => {
|
||||
if (resizeFrame !== null) return
|
||||
const scheduleSync = () => {
|
||||
if (resizeFrame !== null) return
|
||||
resizeFrame = window.requestAnimationFrame(() => {
|
||||
resizeFrame = null
|
||||
void syncMaximized()
|
||||
})
|
||||
}
|
||||
|
||||
resizeFrame = window.requestAnimationFrame(() => {
|
||||
resizeFrame = null
|
||||
void syncMaximized()
|
||||
})
|
||||
}
|
||||
void syncMaximized()
|
||||
|
||||
void syncMaximized()
|
||||
|
||||
appWindow
|
||||
.onResized(() => {
|
||||
scheduleSync()
|
||||
})
|
||||
.then((unlisten) => {
|
||||
unlistenResize = unlisten
|
||||
})
|
||||
.catch(() => {
|
||||
unlistenResize = null
|
||||
})
|
||||
appWindow
|
||||
.onResized(() => scheduleSync())
|
||||
.then((unlisten) => {
|
||||
unlistenResize = unlisten
|
||||
})
|
||||
.catch(() => {
|
||||
unlistenResize = null
|
||||
})
|
||||
})
|
||||
|
||||
return () => {
|
||||
disposed = true
|
||||
if (resizeFrame !== null) {
|
||||
window.cancelAnimationFrame(resizeFrame)
|
||||
}
|
||||
disposeTauriListener(unlistenResize, "WindowControls.resize")
|
||||
unlistenResize?.()
|
||||
}
|
||||
}, [isWindows])
|
||||
|
||||
if (!isWindows) return null
|
||||
|
||||
const appWindow = getCurrentWindow()
|
||||
if (!isWindows || !isDesktop()) return null
|
||||
|
||||
return (
|
||||
<div className="flex h-8 items-stretch [-webkit-app-region:no-drag]">
|
||||
@@ -74,7 +76,7 @@ export function WindowControls() {
|
||||
type="button"
|
||||
className={buttonClass}
|
||||
onClick={() => {
|
||||
appWindow.minimize().catch((err) => {
|
||||
appWindowRef.current?.minimize().catch((err: unknown) => {
|
||||
console.error("[WindowControls] failed to minimize:", err)
|
||||
})
|
||||
}}
|
||||
@@ -87,7 +89,7 @@ export function WindowControls() {
|
||||
type="button"
|
||||
className={buttonClass}
|
||||
onClick={() => {
|
||||
appWindow.toggleMaximize().catch((err) => {
|
||||
appWindowRef.current?.toggleMaximize().catch((err: unknown) => {
|
||||
console.error("[WindowControls] failed to toggle maximize:", err)
|
||||
})
|
||||
}}
|
||||
@@ -103,7 +105,7 @@ export function WindowControls() {
|
||||
"hover:bg-[#e81123] hover:text-white active:bg-[#c50f1f] active:text-white"
|
||||
)}
|
||||
onClick={() => {
|
||||
appWindow.close().catch((err) => {
|
||||
appWindowRef.current?.close().catch((err: unknown) => {
|
||||
console.error("[WindowControls] failed to close:", err)
|
||||
})
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user