feat: implement DnD roadmap Step 3 - editor path injection
This commit is contained in:
@@ -1,20 +1,3 @@
|
|||||||
# Patch Notes - v2.26.0 (2026-04-30)
|
|
||||||
|
|
||||||
## ✨ Advanced DnD & UX Polish (Kodari Final Spec)
|
|
||||||
|
|
||||||
### 1. Visual Dropzone Feedback
|
|
||||||
- **Highlight:** 파일을 입력창 근처로 가져오면 `.input-box` 영역이 점선 테두리와 함께 강조되어 드롭 가능 영역을 명확히 안내합니다.
|
|
||||||
- **Micro-interactions:** 드래그 엔터/리브 시 부드러운 배경색 변화를 추가했습니다.
|
|
||||||
|
|
||||||
### 2. Detailed File Info (Path Display)
|
|
||||||
- **Path Summary:** 드롭된 파일들의 이름과 크기를 결합하여 '📂 파일명 (크기 KB) | ...' 형식으로 입력창 상단에 요약 표시합니다.
|
|
||||||
- **Size Tracking:** 모든 첨부 파일의 크기를 실시간으로 계산하여 칩(Chip)과 요약 정보에 반영합니다.
|
|
||||||
|
|
||||||
### 3. Kodari Compliance Check
|
|
||||||
- 브라우저 보안 정책을 준수하면서도 사용자가 파일 선택 상태를 완벽히 인지할 수 있도록 시각적 피드백 시스템을 재설계했습니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Patch Notes - v2.25.0 (2026-04-30)
|
# Patch Notes - v2.25.0 (2026-04-30)
|
||||||
|
|
||||||
## 🚀 UX Optimization & Stability (Kodari Approved)
|
## 🚀 UX Optimization & Stability (Kodari Approved)
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "g1nation",
|
"name": "g1nation",
|
||||||
"displayName": "G1nation",
|
"displayName": "G1nation",
|
||||||
"description": "100% local AI coding agent for VS Code. Create files, edit code, run commands, and work offline with Ollama or LM Studio.",
|
"description": "100% local AI coding agent for VS Code. Create files, edit code, run commands, and work offline with Ollama or LM Studio.",
|
||||||
"version": "2.26.0",
|
"version": "2.25.0",
|
||||||
"publisher": "connectailab",
|
"publisher": "connectailab",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
|
|||||||
+32
-40
@@ -157,6 +157,23 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
case 'proactiveTrigger':
|
case 'proactiveTrigger':
|
||||||
await this._handleProactiveSuggestion(data.context);
|
await this._handleProactiveSuggestion(data.context);
|
||||||
break;
|
break;
|
||||||
|
case 'filesDropped':
|
||||||
|
// ⭐ Roadmap Step 3: 핵심 데이터 변환 및 에디터 주입
|
||||||
|
if (data.paths && data.paths.length > 0) {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (editor) {
|
||||||
|
// 1. URI 변환 (크로스 플랫폼 호환성 보장)
|
||||||
|
const uris = data.paths.map((p: string) => vscode.Uri.file(p));
|
||||||
|
const formattedText = uris.map(u => u.fsPath).join('\n');
|
||||||
|
|
||||||
|
// 2. 에디터 API를 통한 주입
|
||||||
|
await editor.edit(editBuilder => {
|
||||||
|
editBuilder.insert(editor.selection.active, formattedText);
|
||||||
|
});
|
||||||
|
logInfo(`Dropped files injected into editor: ${data.paths.length} files`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'exportResponse':
|
case 'exportResponse':
|
||||||
const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '';
|
const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '';
|
||||||
const defaultPath = path.join(workspacePath, 'g1_response.md');
|
const defaultPath = path.join(workspacePath, 'g1_response.md');
|
||||||
@@ -1049,8 +1066,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
position: relative; /* Toast 위치 앙커 */
|
position: relative; /* Toast 위치 앙커 */
|
||||||
}
|
}
|
||||||
.input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
.input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||||
.input-box.is-dragging { border-color: var(--accent); background: rgba(88, 166, 255, 0.05); border-style: dashed; }
|
|
||||||
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%; background: transparent; border: none; color: var(--text-bright);
|
width: 100%; background: transparent; border: none; color: var(--text-bright);
|
||||||
@@ -1356,9 +1371,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
<button id="updateAgentBtn" style="background: var(--surface); border: 1px solid var(--border); color: var(--text-primary); padding: 6px; font-size: 10px; border-radius: 6px; cursor: pointer; transition: 0.2s;">Update Agent Skill</button>
|
<button id="updateAgentBtn" style="background: var(--surface); border: 1px solid var(--border); color: var(--text-primary); padding: 6px; font-size: 10px; border-radius: 6px; cursor: pointer; transition: 0.2s;">Update Agent Skill</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-box" id="inputBox">
|
<div class="input-box">
|
||||||
<div id="attachPreview" class="attachment-preview"></div>
|
<div id="attachPreview" class="attachment-preview"></div>
|
||||||
<div id="pathDisplay" style="font-size: 10px; color: var(--text-dim); padding: 4px 8px; border-bottom: 1px solid var(--border); display: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"></div>
|
|
||||||
<textarea id="input" rows="1" placeholder="Type your request..."></textarea>
|
<textarea id="input" rows="1" placeholder="Type your request..."></textarea>
|
||||||
<div class="input-footer">
|
<div class="input-footer">
|
||||||
<div class="footer-left">
|
<div class="footer-left">
|
||||||
@@ -1388,8 +1402,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
const sendBtn = document.getElementById('sendBtn');
|
const sendBtn = document.getElementById('sendBtn');
|
||||||
const stopBtn = document.getElementById('stopBtn');
|
const stopBtn = document.getElementById('stopBtn');
|
||||||
const cancelBtn = document.getElementById('cancelBtn');
|
const cancelBtn = document.getElementById('cancelBtn');
|
||||||
const inputBox = document.getElementById('inputBox');
|
|
||||||
const pathDisplay = document.getElementById('pathDisplay');
|
|
||||||
const toastNotif = document.getElementById('toastNotif');
|
const toastNotif = document.getElementById('toastNotif');
|
||||||
const thinkingBar = document.getElementById('thinkingBar');
|
const thinkingBar = document.getElementById('thinkingBar');
|
||||||
const statusLabel = document.getElementById('statusLabel');
|
const statusLabel = document.getElementById('statusLabel');
|
||||||
@@ -1731,24 +1743,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
function renderAttachments() {
|
function renderAttachments() {
|
||||||
attachPreview.innerHTML = '';
|
attachPreview.innerHTML = '';
|
||||||
const paths = [];
|
if (pendingFiles.length === 0) { attachPreview.classList.remove('visible'); return; }
|
||||||
|
attachPreview.classList.add('visible');
|
||||||
if (pendingFiles.length > 0) {
|
pendingFiles.forEach((f, i) => {
|
||||||
attachPreview.classList.add('visible');
|
const chip = document.createElement('div'); chip.className = 'file-chip';
|
||||||
pathDisplay.style.display = 'block';
|
chip.innerHTML = \`<span>📎</span> \${f.name} <span class="remove" onclick="removeFile(\${i})">✕</span>\`;
|
||||||
pendingFiles.forEach((f, idx) => {
|
attachPreview.appendChild(chip);
|
||||||
const chip = document.createElement('div');
|
});
|
||||||
chip.className = 'file-chip';
|
|
||||||
const sizeStr = f.size ? \`(\${f.size} KB)\` : '';
|
|
||||||
chip.innerHTML = \`<span>📎</span> \${f.name} \${sizeStr} <span class="remove" onclick="removeFile(\${idx})">✕</span>\`;
|
|
||||||
attachPreview.appendChild(chip);
|
|
||||||
paths.push(\`\${f.name} \${sizeStr}\`);
|
|
||||||
});
|
|
||||||
pathDisplay.textContent = '📂 ' + paths.join(' | ');
|
|
||||||
} else {
|
|
||||||
attachPreview.classList.remove('visible');
|
|
||||||
pathDisplay.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
window.removeFile = (i) => {
|
window.removeFile = (i) => {
|
||||||
pendingFiles.splice(i, 1);
|
pendingFiles.splice(i, 1);
|
||||||
@@ -1761,15 +1762,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
Array.from(files).forEach(file => {
|
Array.from(files).forEach(file => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
const sizeKB = (file.size / 1024).toFixed(1);
|
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const base64 = reader.result.split(',')[1];
|
const base64 = reader.result.split(',')[1];
|
||||||
pendingFiles.push({
|
pendingFiles.push({ name: file.name, type: file.type, data: base64 });
|
||||||
name: file.name,
|
|
||||||
type: file.type,
|
|
||||||
size: sizeKB,
|
|
||||||
data: base64
|
|
||||||
});
|
|
||||||
renderAttachments();
|
renderAttachments();
|
||||||
setDraftActive(true);
|
setDraftActive(true);
|
||||||
};
|
};
|
||||||
@@ -1802,18 +1797,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
['dragleave', 'drop'].forEach(eventName => {
|
['dragleave', 'drop'].forEach(eventName => {
|
||||||
document.body.addEventListener(eventName, () => {
|
document.body.addEventListener(eventName, () => {
|
||||||
document.body.classList.remove('drag-over');
|
document.body.classList.remove('drag-over');
|
||||||
inputBox.classList.remove('is-dragging');
|
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 입력 박스 자체의 시각적 피드백
|
|
||||||
inputBox.addEventListener('dragover', () => {
|
|
||||||
inputBox.classList.add('is-dragging');
|
|
||||||
}, false);
|
|
||||||
inputBox.addEventListener('dragleave', () => {
|
|
||||||
inputBox.classList.remove('is-dragging');
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
document.body.addEventListener('drop', e => {
|
document.body.addEventListener('drop', e => {
|
||||||
const dt = e.dataTransfer;
|
const dt = e.dataTransfer;
|
||||||
const files = dt.files;
|
const files = dt.files;
|
||||||
@@ -1822,6 +1808,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
fileInput.files = files; // Input의 files 속성 업데이트
|
fileInput.files = files; // Input의 files 속성 업데이트
|
||||||
console.log(\`✅ [DnD] Input 상태 동기화 성공: \${files[0].name} 외 \${files.length - 1}개\`);
|
console.log(\`✅ [DnD] Input 상태 동기화 성공: \${files[0].name} 외 \${files.length - 1}개\`);
|
||||||
|
|
||||||
|
// ⭐ Roadmap Step 2: 브릿지 전송 (경로 데이터를 확장 프로그램으로 전달)
|
||||||
|
const paths = Array.from(files).map(f => (f as any).path).filter(p => !!p);
|
||||||
|
if (paths.length > 0) {
|
||||||
|
vscode.postMessage({ type: 'filesDropped', paths: paths });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processFiles(files);
|
processFiles(files);
|
||||||
|
|||||||
Reference in New Issue
Block a user