feat: remove Deleted-projects section (delete = permanent removal, files kept) + refresh button on iframe tab windows
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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)}',
|
||||
|
||||
@@ -1076,6 +1076,20 @@
|
||||
(remounted=true)✓;functional-diag 全 20 STEP ERROR_COUNT: 0。
|
||||
- 备注:纯客户端改动,F5 即生效。
|
||||
|
||||
## 补记(移除「已删除项目」区块 + iframe 标签右上角刷新)
|
||||
|
||||
- 用户反馈:① 设置里的「已删除的项目」区块不要了——删除项目就是彻底移出工作台(对话与
|
||||
项目文件本就保留);② 做好的网页窗口(iframe 标签)右上角没有刷新按钮。
|
||||
- 处理:① 移除设置面板「已删除的项目」区块与 readdProject;removeProject 删除时同步清理
|
||||
bindings/folders/views/order 本地关联状态;确认文案改为「彻底移除(对话与项目文件保留)」;
|
||||
② 新增 IframePane 组件(iframe 标签统一外壳):右上角常驻 ↻(重挂载整页刷新),
|
||||
PaneTabBody 的 iframe 分支改走它。
|
||||
- 验证(probe-batch13.cjs):iframe 标签 frameWrap/刷新按钮(title=刷新)✓、点击后 iframe
|
||||
重挂载 ✓;删除常驻项目后 removedSectionGone=true、行数 2→1 ✓;functional-diag 全 20 STEP
|
||||
(STEP10 断言按新行为重写)ERROR_COUNT: 0。
|
||||
- 备注:纯客户端改动,F5 即生效;重启不会自动出现该按钮,必须本次改动生效。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -360,14 +360,10 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
out.layoutGoneAfterDelete=!document.querySelector('.dsh-wt_layout');
|
||||
try{ out.layoutsLeft=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).layouts.length }catch(e){ out.layoutsLeft='ERR' }
|
||||
// 真删除:「已删除的项目」区块出现,重新添加可逆(无恢复默认按钮)
|
||||
// 真删除:「已删除的项目」区块已移除——删除即彻底移出工作台(无重新添加入口)
|
||||
out.removedSection=!!document.querySelector('.dsh-wt_settings .dsh-wt_manageRowRemoved');
|
||||
out.resetBtnGone=!document.querySelector('.dsh-wt_manageReset');
|
||||
var readdBtn=document.querySelector('.dsh-wt_settings .dsh-wt_manageRowRemoved .dsh-wt_manageBtn');
|
||||
if(readdBtn){ readdBtn.click(); }
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
try{ out.removedAfterReadd=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).removed }catch(e){ out.removedAfterReadd='ERR' }
|
||||
out.prBackAfterReadd=document.querySelectorAll('.dsh-wt_projects .pr_card').length;
|
||||
out.prGoneAfterDelete=document.querySelectorAll('.dsh-wt_projects .pr_card').length===0;
|
||||
var done=document.querySelector('.dsh-wt_manageDone');
|
||||
if(done){ done.click(); }
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// probe-batch13.cjs — 已删除项目区块移除 + iframe 标签右上角刷新
|
||||
const http = require('http');
|
||||
const { spawn } = require('child_process');
|
||||
const WebSocket = require('ws');
|
||||
const PORT = 9370;
|
||||
const proc = spawn('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', [
|
||||
'--headless=new', '--disable-gpu', '--no-first-run', '--no-default-browser-check',
|
||||
'--window-size=1440,900', '--force-device-scale-factor=1',
|
||||
'--remote-debugging-port=' + PORT,
|
||||
'--user-data-dir=C:\\Users\\SJL\\AppData\\Local\\Temp\\wt-chrome-batch13',
|
||||
'about:blank',
|
||||
], { stdio: 'ignore' });
|
||||
const getJSON = (p) => new Promise((res, rej) => {
|
||||
http.get({ host: '127.0.0.1', port: PORT, path: p }, (r) => { let d=''; r.on('data',c=>d+=c); r.on('end',()=>{try{res(JSON.parse(d))}catch(e){rej(e)}}) }).on('error', rej);
|
||||
});
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
(async () => {
|
||||
let list = null;
|
||||
for (let i = 0; i < 40; i++) { try { list = await getJSON('/json/list'); if (list && list.length) break; } catch {} await sleep(500); }
|
||||
const target = list.find((t) => t.type === 'page') || list[0];
|
||||
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
||||
let id = 0; const pending = new Map();
|
||||
const send = (method, params) => new Promise((res) => { const mid = ++id; pending.set(mid, res); ws.send(JSON.stringify({ id: mid, method, params })); });
|
||||
ws.on('message', (raw) => { const m = JSON.parse(String(raw)); if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); } });
|
||||
await new Promise((r) => ws.on('open', r));
|
||||
await send('Runtime.enable');
|
||||
await send('Page.enable');
|
||||
await send('Page.navigate', { url: 'http://127.0.0.1:3080/' });
|
||||
const evaluate = async (expr) => {
|
||||
const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true });
|
||||
if (r.result && r.result.exceptionDetails) return { __error: (r.result.exceptionDetails.exception || {}).description || r.result.exceptionDetails.text };
|
||||
return r.result && r.result.result ? r.result.result.value : undefined;
|
||||
};
|
||||
let ready = null;
|
||||
for (let i = 0; i < 30; i++) { await sleep(1000); ready = await evaluate("!!window.__dshWorktable"); if (ready === true) break; }
|
||||
await sleep(1500);
|
||||
const step = await evaluate(`(async function(){
|
||||
var out = {};
|
||||
var sleep = function(ms){ return new Promise(function(r){ setTimeout(r, ms); }); };
|
||||
var vis = function(sel){ return [].slice.call(document.querySelectorAll(sel)).find(function(el){ return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0); }); };
|
||||
var st = window.__dshWorktable.splitStore;
|
||||
try {
|
||||
// A. iframe 标签右上角刷新
|
||||
st.open({id:'t-frame',title:'f',top:null,main:[{id:'p1',title:'W',min:200,content:null}],chatWidth:{default:320,min:240,max:600}});
|
||||
await sleep(600);
|
||||
st.openTab('main', 0, { kind: 'iframe', url: 'https://example.com', title: 'web' });
|
||||
await sleep(900);
|
||||
out.frameWrap = !!vis('.dsh-wt_frameWrap');
|
||||
var rb = vis('.dsh-wt_frameRefresh');
|
||||
out.refreshBtn = !!rb;
|
||||
out.refreshTitle = rb ? rb.getAttribute('title') : null;
|
||||
var f1 = vis('.dsh-wt_frameWrap iframe');
|
||||
if (rb) { rb.click(); }
|
||||
await sleep(700);
|
||||
var f2 = vis('.dsh-wt_frameWrap iframe');
|
||||
out.remounted = !!f1 && !!f2 && f1 !== f2;
|
||||
st.close();
|
||||
await sleep(300);
|
||||
// B. 已删除项目区块:删除常驻项目后设置面板里不得再有该区块
|
||||
var btnS = document.querySelector('.dsh-wt_actions .dsh-wt_iconBtn:nth-child(2)');
|
||||
if (btnS) { btnS.click(); }
|
||||
await sleep(300);
|
||||
var rows = document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow');
|
||||
out.rowsBefore = rows.length;
|
||||
// 删除第一个常驻项目行(其最后一个按钮 = ✕)
|
||||
var xBtn = rows[0] && rows[0].querySelectorAll('.dsh-wt_manageBtn')[rows[0].querySelectorAll('.dsh-wt_manageBtn').length-1];
|
||||
if (xBtn) { xBtn.click(); }
|
||||
await sleep(250);
|
||||
var del = document.querySelector('.dsh-wt_confirmDelete');
|
||||
if (del) { del.click(); }
|
||||
await sleep(350);
|
||||
out.removedSectionGone = !document.querySelector('.dsh-wt_settings .dsh-wt_manageRowRemoved');
|
||||
out.rowsAfter = document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow').length;
|
||||
// 清理:把删除的项目恢复(直接改 localStorage + 刷新页面状态不便——用 removed 还原)
|
||||
var j = JSON.parse(localStorage.getItem('dsh.worktable.projects.v1'));
|
||||
j.removed = [];
|
||||
localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify(j));
|
||||
var done = document.querySelector('.dsh-wt_manageDone');
|
||||
if (done) { done.click(); }
|
||||
await sleep(200);
|
||||
} catch(e) { out.err = String(e); }
|
||||
return JSON.stringify(out);
|
||||
})()`);
|
||||
console.log('BATCH13:', JSON.stringify(step));
|
||||
ws.close(); proc.kill(); process.exit(0);
|
||||
})().catch((e) => { console.error('FATAL', e && e.stack || e); try { proc.kill(); } catch {} process.exit(1); });
|
||||
Reference in New Issue
Block a user