342 lines
9.8 KiB
TypeScript
342 lines
9.8 KiB
TypeScript
// 对象动作统一模型:左侧树、中间列表、Pending Changes、目录对比结果
|
|
// 都转换成 FileTarget,再由 buildFileMenu 按状态生成同一套菜单与置灰规则。
|
|
|
|
import type { MappingInfo } from "~/lib/api"
|
|
|
|
import { resolveLocalPath } from "./mapping"
|
|
import { normalizeServerPath } from "./path"
|
|
|
|
// 对象来源:决定菜单里展示哪一组动作。
|
|
export type FileTargetSource = "tree" | "list" | "pending" | "compare"
|
|
|
|
// 统一的对象目标描述。
|
|
export interface FileTarget {
|
|
source: FileTargetSource
|
|
folder: boolean
|
|
serverPath: string
|
|
/** 依据 Mapping 推导出的本地路径,未映射为 null。 */
|
|
localPath: string | null
|
|
/** 是否处于某条 Mapping 子树内。 */
|
|
mapped: boolean
|
|
/** serverPath 本身是否就是一条 Mapping 的根。 */
|
|
mappingRoot: boolean
|
|
/** 挂起更改状态(pendingEdit / pendingAdd / ...),无挂起为 null。 */
|
|
pendingStatus: string | null
|
|
/** 目录对比结果状态(localOnly / remoteChanged / ...),非对比来源为 null。 */
|
|
compareStatus?: string | null
|
|
}
|
|
|
|
// 全部对象动作。
|
|
export type FileActionId =
|
|
| "map"
|
|
| "unmap"
|
|
| "getLatest"
|
|
| "forceGetLatest"
|
|
| "getSpecificVersion"
|
|
| "checkout"
|
|
| "rename"
|
|
| "delete"
|
|
| "undo"
|
|
| "add"
|
|
| "compare"
|
|
| "history"
|
|
| "viewFile"
|
|
| "diffLocalLatest"
|
|
| "branch"
|
|
| "merge"
|
|
| "revealInFinder"
|
|
| "addLocalFiles"
|
|
| "properties"
|
|
|
|
// 单个菜单项:是否可用与置灰原因。
|
|
export interface FileMenuItem {
|
|
id: FileActionId
|
|
label: string
|
|
enabled: boolean
|
|
danger?: boolean
|
|
/** 置灰原因,展示为菜单项后缀说明。 */
|
|
reason?: string
|
|
}
|
|
|
|
/**
|
|
* 由来源数据构造统一 FileTarget,本地路径与映射状态从 mappings 推导。
|
|
*/
|
|
export function makeFileTarget(input: {
|
|
source: FileTargetSource
|
|
folder: boolean
|
|
serverPath: string
|
|
mappings: MappingInfo[]
|
|
pendingStatus?: string | null
|
|
compareStatus?: string | null
|
|
}): FileTarget {
|
|
const serverPath = normalizeServerPath(input.serverPath)
|
|
const localPath = resolveLocalPath(input.mappings, serverPath)
|
|
const mappingRoot = input.mappings.some(
|
|
(mapping) => normalizeServerPath(mapping.serverPath) === serverPath,
|
|
)
|
|
return {
|
|
source: input.source,
|
|
folder: input.folder,
|
|
serverPath,
|
|
localPath,
|
|
mapped: localPath != null,
|
|
mappingRoot,
|
|
pendingStatus: input.pendingStatus ?? null,
|
|
compareStatus: input.compareStatus ?? null,
|
|
}
|
|
}
|
|
|
|
const NOT_MAPPED_REASON = "未映射到本地"
|
|
|
|
/**
|
|
* 按对象状态生成右键菜单分组:同一路径在树和列表中产出完全一致的动作与置灰规则。
|
|
* Checkin、全局设置等不进入对象菜单。
|
|
*/
|
|
export function buildFileMenu(target: FileTarget): FileMenuItem[][] {
|
|
// 目录对比中的 localOnly 项:服务端不存在,只保留本地查看与加入版本控制。
|
|
if (target.source === "compare" && target.compareStatus === "localOnly") {
|
|
return buildLocalOnlyMenu(target)
|
|
}
|
|
return target.folder ? buildFolderMenu(target) : buildFileItemMenu(target)
|
|
}
|
|
|
|
/**
|
|
* 仅本地存在(localOnly)的对比结果项菜单:加入版本控制 + 本地查看。
|
|
*/
|
|
function buildLocalOnlyMenu(target: FileTarget): FileMenuItem[][] {
|
|
const sections: FileMenuItem[][] = []
|
|
if (!target.folder) {
|
|
sections.push([{ id: "viewFile", label: "查看文件", enabled: true }])
|
|
}
|
|
sections.push([
|
|
{
|
|
id: "add",
|
|
label: "加入版本控制",
|
|
enabled: target.localPath != null,
|
|
reason: target.localPath != null ? undefined : "缺少本地路径",
|
|
},
|
|
])
|
|
return sections
|
|
}
|
|
|
|
/**
|
|
* 目录菜单:本地工作区动作(未映射置灰)+ Mapping 动作 + 服务端动作。
|
|
*/
|
|
function buildFolderMenu(target: FileTarget): FileMenuItem[][] {
|
|
const sections: FileMenuItem[][] = []
|
|
|
|
const workspaceActions: FileMenuItem[] = [
|
|
{
|
|
id: "getLatest",
|
|
label: "获取最新",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "forceGetLatest",
|
|
label: "强制获取最新…",
|
|
enabled: target.mapped,
|
|
danger: true,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "checkout",
|
|
label: "签出编辑",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "rename",
|
|
label: "重命名…",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "delete",
|
|
label: "挂起删除",
|
|
enabled: target.mapped,
|
|
danger: true,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "undo",
|
|
label: "撤销更改",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
]
|
|
sections.push(workspaceActions)
|
|
|
|
const mappingActions: FileMenuItem[] = []
|
|
if (!target.mapped) {
|
|
mappingActions.push({ id: "map", label: "映射到本地", enabled: true })
|
|
}
|
|
if (target.mappingRoot) {
|
|
mappingActions.push({ id: "unmap", label: "取消映射", enabled: true, danger: true })
|
|
}
|
|
if (mappingActions.length > 0) {
|
|
sections.push(mappingActions)
|
|
}
|
|
|
|
sections.push([
|
|
{
|
|
id: "revealInFinder",
|
|
label: "在访达中打开",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "addLocalFiles",
|
|
label: "添加本地文件…",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
])
|
|
|
|
sections.push([
|
|
{
|
|
id: "compare",
|
|
label: "目录对比",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{ id: "history", label: "查看历史", enabled: true },
|
|
{ id: "branch", label: "分支…", enabled: true },
|
|
{ id: "merge", label: "合并…", enabled: true },
|
|
])
|
|
|
|
sections.push(buildAdvancedSection(target))
|
|
|
|
return sections
|
|
}
|
|
|
|
/**
|
|
* 高级动作分组:获取特定版本(按 changeset 覆盖本地)与属性弹窗。
|
|
*/
|
|
function buildAdvancedSection(target: FileTarget): FileMenuItem[] {
|
|
return [
|
|
{
|
|
id: "getSpecificVersion",
|
|
label: "获取特定版本…",
|
|
enabled: target.mapped,
|
|
danger: true,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{ id: "properties", label: "属性…", enabled: true },
|
|
]
|
|
}
|
|
|
|
/**
|
|
* 文件菜单:查看 / 比较 / 历史 + 本地工作区动作,按挂起状态控制可用性。
|
|
* pendingAdd 文件不展示服务器比较与历史入口;pendingEdit 禁止直接 Get Latest 覆盖。
|
|
*/
|
|
function buildFileItemMenu(target: FileTarget): FileMenuItem[][] {
|
|
const sections: FileMenuItem[][] = []
|
|
const pending = target.pendingStatus
|
|
const isPendingAdd = pending === "pendingAdd"
|
|
|
|
const viewActions: FileMenuItem[] = [
|
|
{ id: "viewFile", label: "查看文件", enabled: true },
|
|
]
|
|
if (!isPendingAdd) {
|
|
viewActions.push({
|
|
id: "diffLocalLatest",
|
|
label: "与最新版本比较",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
})
|
|
viewActions.push({ id: "history", label: "查看历史", enabled: true })
|
|
}
|
|
viewActions.push({
|
|
id: "revealInFinder",
|
|
label: "在访达中显示",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
})
|
|
sections.push(viewActions)
|
|
|
|
// Pending Changes 列表项以撤销为主,不再重复签出 / 删除入口。
|
|
if (target.source === "pending") {
|
|
sections.push([
|
|
{ id: "undo", label: "撤销更改", enabled: true, danger: true },
|
|
])
|
|
// pendingAdd 项服务端尚不存在,属性查询无意义。
|
|
if (!isPendingAdd) {
|
|
sections.push([{ id: "properties", label: "属性…", enabled: true }])
|
|
}
|
|
return sections
|
|
}
|
|
|
|
const workspaceActions: FileMenuItem[] = [
|
|
{
|
|
// 安全模式获取:本地改动会产生冲突而不会被覆盖,挂起状态下也允许执行。
|
|
id: "getLatest",
|
|
label: "获取最新",
|
|
enabled: target.mapped,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "forceGetLatest",
|
|
label: "强制获取最新…",
|
|
enabled: target.mapped,
|
|
danger: true,
|
|
reason: target.mapped ? undefined : NOT_MAPPED_REASON,
|
|
},
|
|
{
|
|
id: "checkout",
|
|
label: "签出编辑",
|
|
enabled: target.mapped && pending == null,
|
|
reason: !target.mapped
|
|
? NOT_MAPPED_REASON
|
|
: pending === "pendingEdit"
|
|
? "已签出"
|
|
: pending != null
|
|
? "存在挂起更改"
|
|
: undefined,
|
|
},
|
|
{
|
|
id: "rename",
|
|
label: "重命名…",
|
|
enabled: target.mapped && pending !== "pendingAdd" && pending !== "pendingDelete" && pending !== "pendingRename",
|
|
reason: !target.mapped
|
|
? NOT_MAPPED_REASON
|
|
: pending === "pendingAdd"
|
|
? "挂起新增,签入后才能重命名"
|
|
: pending === "pendingDelete"
|
|
? "已挂起删除"
|
|
: pending === "pendingRename"
|
|
? "已挂起重命名"
|
|
: undefined,
|
|
},
|
|
{
|
|
id: "delete",
|
|
label: "挂起删除",
|
|
enabled: target.mapped && pending !== "pendingDelete",
|
|
danger: true,
|
|
reason: !target.mapped
|
|
? NOT_MAPPED_REASON
|
|
: pending === "pendingDelete"
|
|
? "已挂起删除"
|
|
: undefined,
|
|
},
|
|
{
|
|
id: "undo",
|
|
label: "撤销更改",
|
|
enabled: pending != null,
|
|
reason: pending == null ? "没有挂起更改" : undefined,
|
|
},
|
|
]
|
|
sections.push(workspaceActions)
|
|
|
|
// pendingAdd 文件服务端尚不存在,不提供分支、合并、属性与获取特定版本。
|
|
if (!isPendingAdd) {
|
|
sections.push([
|
|
{ id: "branch", label: "分支…", enabled: true },
|
|
{ id: "merge", label: "合并…", enabled: true },
|
|
])
|
|
sections.push(buildAdvancedSection(target))
|
|
}
|
|
|
|
return sections
|
|
}
|