코드 에디터: 터미널 버튼 아래에 에뮬레이터 버튼 추가
.ino 파일이 열려 있으면 자동 컴파일 후 에뮬레이터 탭 열기, 아니면 에뮬레이터 창만 열기. 컴파일 중 버튼 텍스트로 상태 표시. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+50
-2
@@ -192,8 +192,9 @@
|
||||
<div id="sidebar-code" style="display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden">
|
||||
<!-- 상단 탭: 파일 | 설정 -->
|
||||
<div class="csb-tab-bar">
|
||||
<button class="code-sb-tab active" id="csb-tab-files" onclick="setCodeSbTab('files')">📄 파일</button>
|
||||
<button class="code-sb-tab" id="csb-tab-settings"onclick="setCodeSbTab('settings')">⚙️ 설정</button>
|
||||
<button class="code-sb-tab active" id="csb-tab-files" onclick="setCodeSbTab('files')">📄 파일</button>
|
||||
<button class="code-sb-tab" id="csb-tab-libraries" onclick="setCodeSbTab('libraries')">📦</button>
|
||||
<button class="code-sb-tab" id="csb-tab-settings" onclick="setCodeSbTab('settings')">⚙️</button>
|
||||
</div>
|
||||
|
||||
<!-- 파일 탭: 상하 분할 -->
|
||||
@@ -254,10 +255,57 @@
|
||||
<div class="csb-term-row">
|
||||
<button id="code-preview-toggle-btn" class="csb-term-btn" onclick="toggleCodePreview()" title="브라우저 미리보기 패널">🌐 미리보기</button>
|
||||
</div>
|
||||
<!-- Arduino 에뮬레이터 -->
|
||||
<div class="csb-term-row">
|
||||
<button id="code-emu-btn" class="csb-term-btn" onclick="openArduinoEmulator()" title="Arduino 에뮬레이터 열기">🔌 에뮬레이터</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 라이브러리/보드 탭 -->
|
||||
<div id="csb-libraries" style="display:none;flex-direction:column;flex:1;min-height:0">
|
||||
<!-- 상단 섹션 탭: 라이브러리 | 보드 -->
|
||||
<div style="display:flex;border-bottom:2px solid var(--line);flex-shrink:0;background:var(--panel-2)">
|
||||
<button class="code-sb-tab active" id="csb-ardsec-lib" onclick="csbArdSection('lib')" style="flex:1;font-size:11px;padding:7px 0">📦 라이브러리</button>
|
||||
<button class="code-sb-tab" id="csb-ardsec-board" onclick="csbArdSection('board')" style="flex:1;font-size:11px;padding:7px 0">🔌 보드</button>
|
||||
</div>
|
||||
|
||||
<!-- ── 라이브러리 패널 ── -->
|
||||
<div id="csb-ard-lib" style="display:flex;flex-direction:column;flex:1;min-height:0">
|
||||
<div style="display:flex;border-bottom:1px solid var(--line);flex-shrink:0">
|
||||
<button class="code-sb-tab active" id="csb-libtab-installed" onclick="csbLibTab('installed')" style="flex:1;font-size:10px;padding:5px 0">설치됨</button>
|
||||
<button class="code-sb-tab" id="csb-libtab-search" onclick="csbLibTab('search')" style="flex:1;font-size:10px;padding:5px 0">검색/설치</button>
|
||||
</div>
|
||||
<div id="csb-lib-search-row" style="display:none;padding:6px 8px;border-bottom:1px solid var(--line);gap:5px;flex-shrink:0;align-items:center">
|
||||
<input id="csb-lib-q" placeholder="키워드 또는 라이브러리명..." onkeydown="if(event.key==='Enter')csbLibSearch()"
|
||||
style="flex:1;font-size:11px;padding:5px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:6px;font-family:inherit;min-width:0">
|
||||
<button onclick="csbLibSearch()" class="csb-term-btn" style="padding:5px 8px;font-size:11px;flex-shrink:0">검색</button>
|
||||
</div>
|
||||
<div id="csb-lib-list" style="flex:1;overflow-y:auto;padding:4px 0">
|
||||
<div style="padding:16px 12px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>
|
||||
</div>
|
||||
<div id="csb-lib-status" style="padding:5px 10px;font-size:10px;color:var(--brand);border-top:1px solid var(--line);min-height:24px;flex-shrink:0"></div>
|
||||
</div>
|
||||
|
||||
<!-- ── 보드 매니저 패널 ── -->
|
||||
<div id="csb-ard-board" style="display:none;flex-direction:column;flex:1;min-height:0">
|
||||
<div style="display:flex;border-bottom:1px solid var(--line);flex-shrink:0">
|
||||
<button class="code-sb-tab active" id="csb-boardtab-installed" onclick="csbBoardTab('installed')" style="flex:1;font-size:10px;padding:5px 0">설치됨</button>
|
||||
<button class="code-sb-tab" id="csb-boardtab-search" onclick="csbBoardTab('search')" style="flex:1;font-size:10px;padding:5px 0">검색/설치</button>
|
||||
</div>
|
||||
<div id="csb-board-search-row" style="display:none;padding:6px 8px;border-bottom:1px solid var(--line);gap:5px;flex-shrink:0;align-items:center">
|
||||
<input id="csb-board-q" placeholder="보드 이름 (예: esp32, rp2040...)" onkeydown="if(event.key==='Enter')csbBoardSearch()"
|
||||
style="flex:1;font-size:11px;padding:5px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:6px;font-family:inherit;min-width:0">
|
||||
<button onclick="csbBoardSearch()" class="csb-term-btn" style="padding:5px 8px;font-size:11px;flex-shrink:0">검색</button>
|
||||
</div>
|
||||
<div id="csb-board-list" style="flex:1;overflow-y:auto;padding:4px 0">
|
||||
<div style="padding:16px 12px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>
|
||||
</div>
|
||||
<div id="csb-board-status" style="padding:5px 10px;font-size:10px;color:var(--brand);border-top:1px solid var(--line);min-height:24px;flex-shrink:0"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 설정 탭 -->
|
||||
<div id="csb-settings" style="display:none;flex-direction:column;flex:1;min-height:0;overflow-y:auto;padding:14px 12px;gap:14px">
|
||||
<div>
|
||||
|
||||
+298
-1
@@ -884,6 +884,36 @@ function toggleCodePreview() {
|
||||
}
|
||||
if (monacoEditor) setTimeout(() => monacoEditor.layout(), 60);
|
||||
}
|
||||
function openArduinoEmulator() {
|
||||
const file = codeFiles.find(f => f.id === activeCodeFileId);
|
||||
const btn = document.getElementById('code-emu-btn');
|
||||
|
||||
// .ino 파일이 열려 있으면 컴파일 후 에뮬레이터 실행
|
||||
if (file && file.name.endsWith('.ino')) {
|
||||
if (btn) { btn.textContent = '⏳ 컴파일 중…'; btn.disabled = true; }
|
||||
fetch('/api/arduino/compile', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders({}) },
|
||||
body: JSON.stringify({ code: file.content, board: 'uno' }),
|
||||
}).then(r => r.json()).then(data => {
|
||||
if (btn) { btn.textContent = '🔌 에뮬레이터'; btn.disabled = false; }
|
||||
if (data.error) { codeAiAppend('system', '⚠️ 컴파일 오류:\n' + data.error); return; }
|
||||
sessionStorage.setItem('arduino_sketch', file.content);
|
||||
sessionStorage.setItem('arduino_hex', data.hex);
|
||||
sessionStorage.setItem('arduino_filename', file.name);
|
||||
sessionStorage.setItem('arduino_board', 'uno');
|
||||
const w = window.open('/arduino-emulator.html', 'arduino-emulator');
|
||||
if (!w) codeAiAppend('system', '⚠️ 팝업 차단됨 — 브라우저에서 팝업을 허용해주세요');
|
||||
}).catch(e => {
|
||||
if (btn) { btn.textContent = '🔌 에뮬레이터'; btn.disabled = false; }
|
||||
codeAiAppend('system', '⚠️ 서버 오류: ' + e.message);
|
||||
});
|
||||
} else {
|
||||
// .ino 아니면 그냥 에뮬레이터 창만 열기
|
||||
window.open('/arduino-emulator.html', 'arduino-emulator');
|
||||
}
|
||||
}
|
||||
|
||||
function codePreviewRefresh() {
|
||||
const urlInput = document.getElementById('code-preview-url');
|
||||
const iframe = document.getElementById('code-preview-iframe');
|
||||
@@ -2551,6 +2581,11 @@ function _buildCodeSystemPrompt() {
|
||||
- Glob으로 파일 목록 검색, Grep으로 코드 내용 검색이 가능합니다.
|
||||
- Bash는 companion.py 연결 시 터미널 명령을 실행합니다.
|
||||
|
||||
## 파일 경로 규칙 (중요)
|
||||
- 파일 경로에 절대경로·Windows 경로를 절대 사용하지 마세요: Documents/, Desktop/, C:\\, /home/ 등 금지.
|
||||
- 프로젝트 루트 기준 상대 경로만 사용하세요. 예: \`main.ino\`, \`src/utils.cpp\`, \`lib/helper.h\`
|
||||
- 새 프로젝트를 만들면 해당 폴더가 루트입니다. 별도의 상위 폴더를 만들지 마세요.
|
||||
|
||||
## 응답 원칙
|
||||
- 행동 중심으로 응답하세요: 설명만 하지 말고 실제로 파일을 수정하세요.
|
||||
- 변경 사항은 간결하게 설명하고, 코드 블록으로 전체 내용을 반복하지 마세요.
|
||||
@@ -3944,13 +3979,275 @@ let codeSbCurrentTab = 'files';
|
||||
|
||||
function setCodeSbTab(tab) {
|
||||
codeSbCurrentTab = tab;
|
||||
['files','settings'].forEach(t => {
|
||||
['files','settings','libraries'].forEach(t => {
|
||||
const btn = document.getElementById('csb-tab-' + t);
|
||||
if (btn) btn.classList.toggle('active', t === tab);
|
||||
const el = document.getElementById('csb-' + t);
|
||||
if (el) el.style.display = t === tab ? 'flex' : 'none';
|
||||
});
|
||||
if (tab === 'files') csbLoadModels();
|
||||
if (tab === 'libraries') {
|
||||
if (_csbArdSection === 'lib') csbLibLoad();
|
||||
if (_csbArdSection === 'board') csbBoardLoad();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 라이브러리/보드 매니저 (code editor sidebar) ──────────────────────────────
|
||||
let _csbLibCurrentTab = 'installed';
|
||||
let _csbArdSection = 'lib';
|
||||
|
||||
function csbArdSection(sec) {
|
||||
_csbArdSection = sec;
|
||||
['lib','board'].forEach(s => {
|
||||
const btn = document.getElementById('csb-ardsec-' + s);
|
||||
if (btn) btn.classList.toggle('active', s === sec);
|
||||
const el = document.getElementById('csb-ard-' + s);
|
||||
if (el) el.style.display = s === sec ? 'flex' : 'none';
|
||||
});
|
||||
if (sec === 'lib') csbLibLoad();
|
||||
if (sec === 'board') csbBoardLoad();
|
||||
}
|
||||
|
||||
function csbLibTab(tab) {
|
||||
_csbLibCurrentTab = tab;
|
||||
['installed','search'].forEach(t => {
|
||||
const btn = document.getElementById('csb-libtab-' + t);
|
||||
if (btn) btn.classList.toggle('active', t === tab);
|
||||
});
|
||||
const sr = document.getElementById('csb-lib-search-row');
|
||||
if (sr) sr.style.display = tab === 'search' ? 'flex' : 'none';
|
||||
document.getElementById('csb-lib-status').textContent = '';
|
||||
if (tab === 'installed') csbLibLoad();
|
||||
else _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">키워드를 입력하세요.</div>';
|
||||
}
|
||||
|
||||
function _csbLibListEl() { return document.getElementById('csb-lib-list'); }
|
||||
function _csbLibStatus(msg) { const el = document.getElementById('csb-lib-status'); if (el) el.textContent = msg; }
|
||||
function _csbEsc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
|
||||
|
||||
async function csbLibLoad() {
|
||||
_csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>';
|
||||
try {
|
||||
const r = await fetch('/api/arduino/libs', { headers: authHeaders() });
|
||||
const data = await r.json();
|
||||
_csbLibRenderInstalled(data.libs || []);
|
||||
} catch { _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
|
||||
}
|
||||
|
||||
function _csbLibRenderInstalled(libs) {
|
||||
const el = _csbLibListEl();
|
||||
if (!libs.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">설치된 라이브러리 없음</div>'; return; }
|
||||
el.innerHTML = libs.map(l => {
|
||||
const sid = 'csbex_' + l.name.replace(/\W/g,'_');
|
||||
const exRows = (l.examples||[]).map(ex =>
|
||||
`<div style="display:flex;align-items:center;padding:3px 0;gap:6px">
|
||||
<span style="font-size:10px;color:var(--muted);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">📄 ${_csbEsc(ex)}</span>
|
||||
<button onclick="csbLibOpenExample('${_csbEsc(l.name)}','${_csbEsc(ex)}')"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:var(--brand);border-radius:4px;cursor:pointer;font-family:inherit;white-space:nowrap">열기</button>
|
||||
</div>`).join('');
|
||||
const hasEx = l.examples?.length > 0;
|
||||
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line)">
|
||||
<div style="display:flex;align-items:flex-start;gap:6px">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(l.name)}</div>
|
||||
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(l.author||'')} · v${_csbEsc(l.version||'?')}${l.category?' · '+_csbEsc(l.category):''}</div>
|
||||
${l.sentence?`<div style="font-size:10px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${_csbEsc(l.sentence)}</div>`:''}
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;flex-shrink:0">
|
||||
${hasEx?`<button onclick="document.getElementById('${sid}').style.display=document.getElementById('${sid}').style.display==='none'?'block':'none'"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:var(--muted);border-radius:4px;cursor:pointer;font-family:inherit">예제</button>`:''}
|
||||
<button onclick="csbLibUninstall('${_csbEsc(l.name)}')"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:#f87171;border-radius:4px;cursor:pointer;font-family:inherit">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
${hasEx?`<div id="${sid}" style="display:none;margin-top:5px;padding:5px 8px;background:var(--panel-2);border-radius:5px">${exRows}</div>`:''}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function csbLibOpenExample(libName, exName) {
|
||||
_csbLibStatus(`예제 로딩: ${exName}...`);
|
||||
try {
|
||||
const r = await fetch(`/api/arduino/libs/example?lib=${encodeURIComponent(libName)}&example=${encodeURIComponent(exName)}`, { headers: authHeaders() });
|
||||
const data = await r.json();
|
||||
if (data.error) { _csbLibStatus('오류: ' + data.error); return; }
|
||||
_csbLibStatus('');
|
||||
codeNewFile(data.name, data.code);
|
||||
setCodeSbTab('files');
|
||||
} catch(e) { _csbLibStatus('오류: ' + e.message); }
|
||||
}
|
||||
|
||||
async function csbLibSearch() {
|
||||
const q = (document.getElementById('csb-lib-q')?.value || '').trim();
|
||||
if (!q) return;
|
||||
_csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 중...</div>';
|
||||
_csbLibStatus('');
|
||||
try {
|
||||
const r = await fetch('/api/arduino/libs/search?q=' + encodeURIComponent(q), { headers: authHeaders() });
|
||||
const data = await r.json();
|
||||
_csbLibRenderSearch(data.libs || []);
|
||||
} catch { _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
|
||||
}
|
||||
|
||||
function _csbLibRenderSearch(libs) {
|
||||
const el = _csbLibListEl();
|
||||
if (!libs.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 결과 없음</div>'; return; }
|
||||
el.innerHTML = libs.map(l => `
|
||||
<div style="padding:8px 10px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;gap:6px">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="display:flex;align-items:center;gap:5px">
|
||||
<span style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(l.name)}</span>
|
||||
${l.category?`<span style="font-size:9px;padding:1px 5px;border:1px solid var(--line);border-radius:3px;color:var(--muted)">${_csbEsc(l.category)}</span>`:''}
|
||||
</div>
|
||||
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(l.author||'')} · v${_csbEsc(l.version||'?')}</div>
|
||||
${l.sentence?`<div style="font-size:10px;color:var(--muted);margin-top:2px">${_csbEsc(l.sentence)}</div>`:''}
|
||||
</div>
|
||||
<div style="flex-shrink:0;padding-top:2px">
|
||||
${l.installed
|
||||
? `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`
|
||||
: `<button onclick="csbLibInstall('${_csbEsc(l.name)}',this)"
|
||||
style="font-size:10px;padding:3px 8px;background:var(--brand);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:inherit">설치</button>`}
|
||||
</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
async function csbLibInstall(name, btn) {
|
||||
_csbLibStatus(`설치 중: ${name}...`);
|
||||
if (btn) { btn.disabled = true; btn.textContent = '...'; }
|
||||
try {
|
||||
const r = await fetch('/api/arduino/libs/install', {
|
||||
method: 'POST', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.error) { _csbLibStatus('오류: ' + data.error.slice(0,100)); if(btn){btn.disabled=false;btn.textContent='설치';} return; }
|
||||
_csbLibStatus(`✅ ${name} 설치 완료`);
|
||||
if (btn) btn.outerHTML = `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`;
|
||||
} catch(e) { _csbLibStatus('오류: ' + e.message); if(btn){btn.disabled=false;btn.textContent='설치';} }
|
||||
}
|
||||
|
||||
async function csbLibUninstall(name) {
|
||||
if (!confirm(`"${name}" 을 삭제할까요?`)) return;
|
||||
_csbLibStatus(`삭제 중: ${name}...`);
|
||||
try {
|
||||
const r = await fetch('/api/arduino/libs/uninstall', {
|
||||
method: 'DELETE', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.error) { _csbLibStatus('오류: ' + data.error.slice(0,100)); return; }
|
||||
_csbLibStatus(`✅ ${name} 삭제 완료`);
|
||||
csbLibLoad();
|
||||
} catch(e) { _csbLibStatus('오류: ' + e.message); }
|
||||
}
|
||||
|
||||
// ── 보드 매니저 ───────────────────────────────────────────────────────────────
|
||||
let _csbBoardCurrentTab = 'installed';
|
||||
|
||||
function csbBoardTab(tab) {
|
||||
_csbBoardCurrentTab = tab;
|
||||
['installed','search'].forEach(t => {
|
||||
const btn = document.getElementById('csb-boardtab-' + t);
|
||||
if (btn) btn.classList.toggle('active', t === tab);
|
||||
});
|
||||
const sr = document.getElementById('csb-board-search-row');
|
||||
if (sr) sr.style.display = tab === 'search' ? 'flex' : 'none';
|
||||
document.getElementById('csb-board-status').textContent = '';
|
||||
if (tab === 'installed') csbBoardLoad();
|
||||
else { document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">키워드를 입력하세요.</div>'; }
|
||||
}
|
||||
|
||||
function _csbBoardStatus(msg) { const el = document.getElementById('csb-board-status'); if (el) el.textContent = msg; }
|
||||
|
||||
async function csbBoardLoad() {
|
||||
document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>';
|
||||
try {
|
||||
const r = await fetch('/api/arduino/cores', { headers: authHeaders() });
|
||||
const data = await r.json();
|
||||
_csbBoardRenderInstalled(data.cores || []);
|
||||
} catch { document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
|
||||
}
|
||||
|
||||
function _csbBoardRenderInstalled(cores) {
|
||||
const el = document.getElementById('csb-board-list');
|
||||
if (!cores.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">설치된 보드 없음</div>'; return; }
|
||||
el.innerHTML = cores.map(c => {
|
||||
const boardList = c.boards.slice(0,6).map(b => `<span style="font-size:10px;color:var(--muted);background:var(--panel-2);border:1px solid var(--line);border-radius:3px;padding:1px 5px">${_csbEsc(b)}</span>`).join(' ');
|
||||
const more = c.boards.length > 6 ? `<span style="font-size:10px;color:var(--muted)">+${c.boards.length-6}개</span>` : '';
|
||||
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line)">
|
||||
<div style="display:flex;align-items:flex-start;gap:6px">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(c.name)}</div>
|
||||
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(c.id)} · v${_csbEsc(c.version)} · ${_csbEsc(c.maintainer)}</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-top:5px">${boardList}${more}</div>
|
||||
</div>
|
||||
<button onclick="csbBoardUninstall('${_csbEsc(c.id)}')"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:#f87171;border-radius:4px;cursor:pointer;font-family:inherit;flex-shrink:0">삭제</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function csbBoardSearch() {
|
||||
const q = (document.getElementById('csb-board-q')?.value || '').trim();
|
||||
document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 중...</div>';
|
||||
_csbBoardStatus('');
|
||||
try {
|
||||
const r = await fetch('/api/arduino/cores/search?q=' + encodeURIComponent(q), { headers: authHeaders() });
|
||||
const data = await r.json();
|
||||
_csbBoardRenderSearch(data.cores || []);
|
||||
} catch { document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
|
||||
}
|
||||
|
||||
function _csbBoardRenderSearch(cores) {
|
||||
const el = document.getElementById('csb-board-list');
|
||||
if (!cores.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 결과 없음</div>'; return; }
|
||||
el.innerHTML = cores.map(c => {
|
||||
const boardList = c.boards.slice(0,4).map(b => `<span style="font-size:9px;color:var(--muted);background:var(--panel-2);border:1px solid var(--line);border-radius:3px;padding:1px 4px">${_csbEsc(b)}</span>`).join(' ');
|
||||
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;gap:6px">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(c.name)}</div>
|
||||
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(c.id)} · v${_csbEsc(c.version)}</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-top:4px">${boardList}${c.boards.length>4?`<span style="font-size:9px;color:var(--muted)">+${c.boards.length-4}개</span>`:''}</div>
|
||||
</div>
|
||||
<div style="flex-shrink:0;padding-top:2px">
|
||||
${c.installed
|
||||
? `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`
|
||||
: `<button onclick="csbBoardInstall('${_csbEsc(c.id)}',this)"
|
||||
style="font-size:10px;padding:3px 8px;background:var(--brand);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:inherit">설치</button>`}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function csbBoardInstall(id, btn) {
|
||||
_csbBoardStatus(`설치 중: ${id} (수 분 소요)...`);
|
||||
if (btn) { btn.disabled = true; btn.textContent = '...'; }
|
||||
try {
|
||||
const r = await fetch('/api/arduino/cores/install', {
|
||||
method: 'POST', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.error) { _csbBoardStatus('오류: ' + data.error.slice(0,150)); if(btn){btn.disabled=false;btn.textContent='설치';} return; }
|
||||
_csbBoardStatus(`✅ ${id} 설치 완료`);
|
||||
if (btn) btn.outerHTML = `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`;
|
||||
} catch(e) { _csbBoardStatus('오류: ' + e.message); if(btn){btn.disabled=false;btn.textContent='설치';} }
|
||||
}
|
||||
|
||||
async function csbBoardUninstall(id) {
|
||||
if (!confirm(`"${id}" 보드를 삭제할까요?`)) return;
|
||||
_csbBoardStatus(`삭제 중: ${id}...`);
|
||||
try {
|
||||
const r = await fetch('/api/arduino/cores/uninstall', {
|
||||
method: 'DELETE', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.error) { _csbBoardStatus('오류: ' + data.error.slice(0,100)); return; }
|
||||
_csbBoardStatus(`✅ ${id} 삭제 완료`);
|
||||
csbBoardLoad();
|
||||
} catch(e) { _csbBoardStatus('오류: ' + e.message); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user