feat: remove Deleted-projects section (delete = permanent removal, files kept) + refresh button on iframe tab windows

This commit is contained in:
Aisland
2026-08-20 22:29:39 +08:00
parent 3d7efadfe2
commit 47c5ccc034
7 changed files with 140 additions and 34 deletions
+14 -25
View File
@@ -1352,13 +1352,7 @@ function buildCustomLayoutPrompt(req: string): string {
})
}
/** 重新添加已删除的入驻项目(删除 ↔ 重新添加 完全可逆) */
const readdProject = (id: string) => {
persistProjects((prev) => ({
...prev,
removed: prev.removed.filter((x) => x !== id),
}))
}
// ── 快捷方式(表单已移除,仅保留存量条目的删除能力) ──
const removeShortcut = (id: string) => {
@@ -1441,13 +1435,20 @@ function buildCustomLayoutPrompt(req: string): string {
return def ? def.id : '2h'
}
// ── 删除(全部走二次确认;常驻项目 = 移出工作台,插件不卸载) ──
// ── 删除(全部走二次确认;项目彻底移出工作台:对话与项目文件均保留,仅清理本地关联状态) ──
const removeProject = (id: string) => {
persistProjects((prev) => ({
...prev,
removed: prev.removed.includes(id) ? prev.removed : [...prev.removed, id],
hidden: prev.hidden.filter((x) => x !== id),
}))
persistProjects((prev) => {
const next = {
...prev,
removed: prev.removed.includes(id) ? prev.removed : [...prev.removed, id],
hidden: prev.hidden.filter((x) => x !== id),
order: prev.order.filter((x) => x !== id),
}
if (next.bindings[id]) { next.bindings = { ...next.bindings }; delete next.bindings[id] }
if (next.folders[id]) { next.folders = { ...next.folders }; delete next.folders[id] }
if (next.views[id]) { next.views = { ...next.views }; delete next.views[id] }
return next
})
}
const askDelete = (kind: 'layout' | 'shortcut' | 'project', id: string, name: string) => {
setRequestDelete({ kind, id, name })
@@ -1925,18 +1926,6 @@ function buildCustomLayoutPrompt(req: string): string {
<button type="button" className="dsh-wt_manageBtn" title={t('manage.deleteShortcut')} onClick={() => askDelete('shortcut', s.id, s.name)}>✕</button>
</div>
))}
{projects.removed.length > 0 && (
<>
<div className="dsh-wt_menuSep" />
<span className="dsh-wt_menuLabel">{t('manage.removed')}</span>
{projects.removed.map((rid) => (
<div key={rid} className="dsh-wt_manageRow dsh-wt_manageRowOff dsh-wt_manageRowRemoved">
<span className="dsh-wt_manageScName">{projects.nameOverrides[rid] ?? metas[rid]?.name ?? rid}</span>
<button type="button" className="dsh-wt_manageBtn" title={t('manage.readd')} onClick={() => readdProject(rid)}>↺</button>
</div>
))}
</>
)}
</div>
)}
+2 -2
View File
@@ -55,7 +55,7 @@ export const zh = {
'confirm.delete': '删除',
'confirm.layoutBody': '确定删除「{name}」?该工作区及其布局将被移除,此操作不可恢复。',
'confirm.shortcutBody': '确定删除快捷方式「{name}」?此操作不可恢复。',
'confirm.projectBody': '确定从工作台删除「{name}」?插件本身不会被卸载,「恢复默认」也不会复活它;之后可在设置里「已删除的项目」中重新添加。',
'confirm.projectBody': '「{name}」将从工作台彻底移除(对话与项目文件都保留,只是不再显示项目卡片)。',
'pane.browser': '浏览器',
'pane.explorer': '资源管理器',
'pane.scm': '源代码管理',
@@ -189,7 +189,7 @@ export const en = {
'confirm.delete': 'Delete',
'confirm.layoutBody': 'Delete "{name}"? Its workspace and layout will be removed. This cannot be undone.',
'confirm.shortcutBody': 'Delete shortcut "{name}"? This cannot be undone.',
'confirm.projectBody': 'Remove "{name}" from the worktable? The plugin stays installed; Reset defaults will NOT bring it back — re-add it later under Removed projects in Settings.',
'confirm.projectBody': '"{name}" will be removed from the worktable permanently (the conversation and project files are kept).',
'pane.browser': 'Browser',
'pane.explorer': 'Explorer',
'pane.scm': 'Source control',
+18 -1
View File
@@ -891,6 +891,23 @@ function BrowserPane() {
)
}
/** iframe 内容标签(网页/站点产物):右上角常驻刷新按钮(重挂载整页刷新,跨域可靠) */
function IframePane(props: { url: string; title?: string }) {
const [reloadKey, setReloadKey] = useState(0)
return (
<div className="dsh-wt_frameWrap">
<iframe key={reloadKey} className="dsh-wt_paneFrame" src={props.url} title={props.title ?? ''} />
<button
type="button"
className="dsh-wt_frameRefresh"
title={T('pane.refresh')}
aria-label={T('pane.refresh')}
onClick={() => setReloadKey((k) => k + 1)}
>↻</button>
</div>
)
}
/** 动画播放窗:iframe 壳 + 地址栏(站内自带项目/场景列表、播放、画幅切换、导出等全部控件) */
function AnimPane() {
const [url, setUrl] = useState('')
@@ -1534,7 +1551,7 @@ function CustomPane(props: { paneTitle?: string }) {
function PaneTabBody(props: { tab: PaneTab; row: PaneRow; index: number; paneTitle?: string }) {
const content = props.tab.content
if (content.kind === 'iframe') {
return <iframe className="dsh-wt_paneFrame" src={content.url} title={props.tab.title} />
return <IframePane url={content.url} title={content.title ?? props.tab.title} />
}
if (content.kind === 'file') {
return <FileViewer path={content.path} />
+4
View File
@@ -138,6 +138,10 @@ export const css = xtermCss + '\n' + [
'.dsh-wt_paneBar{flex:none;display:flex;align-items:center;height:22px;padding:0 8px;border-bottom:1px solid var(--dsw-alias-border-l1,#262b36);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02))}',
'.dsh-wt_paneTitle{flex:1;min-width:0;font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary,#9aa4b2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
'.dsh-wt_paneFrame{flex:1;width:100%;border:0;background:#010409;min-height:0}',
// iframe 内容标签:容器 + 右上角常驻刷新按钮
'.dsh-wt_frameWrap{position:relative;flex:1;min-height:0;display:flex;flex-direction:column}',
'.dsh-wt_frameRefresh{position:absolute;top:6px;right:6px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l2,#3a4150);border-radius:6px;background:var(--dsw-alias-fill-l2,#171b22);color:var(--dsw-alias-label-primary,#e6e8eb);font-size:13px;line-height:1;cursor:pointer;opacity:.72;z-index:5}',
'.dsh-wt_frameRefresh:hover{opacity:1;border-color:var(--dsw-alias-state-accent-primary,#4f8ef7)}',
'.dsh-wt_splitDivider{background:var(--dsw-alias-border-l2,#3a4150);cursor:col-resize;touch-action:none;box-sizing:border-box}',
'.dsh-wt_splitDividerH{cursor:row-resize}',
'.dsh-wt_splitDivider:hover,.dsh-wt_splitDividerH:hover{background:var(--dsw-alias-state-accent-primary,#4f8ef7)}',