Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions desktop/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// answers, then points a BrowserWindow at it.
'use strict';

const { app, BrowserWindow, shell, dialog, Menu } = require('electron');
const { app, BrowserWindow, shell, dialog, Menu, ipcMain } = require('electron');
const { spawn } = require('child_process');
const http = require('http');
const https = require('https');
Expand Down Expand Up @@ -120,15 +120,38 @@ function waitForServer(port, timeoutMs) {
});
}

// Native folder picker for the "Add project" flow. Returns the chosen absolute
// path, or null on cancel. Registered once (guarded) so re-creating the window
// doesn't stack duplicate handlers.
let _pickFolderRegistered = false;
function registerIpc() {
if (_pickFolderRegistered) return;
_pickFolderRegistered = true;
ipcMain.handle('codbash:pick-folder', async function () {
const res = await dialog.showOpenDialog(win, {
title: 'Choose a project folder',
properties: ['openDirectory', 'createDirectory'],
});
if (res.canceled || !res.filePaths || !res.filePaths.length) return null;
return res.filePaths[0];
});
}

async function createWindow() {
registerIpc();
win = new BrowserWindow({
width: 1320,
height: 860,
minWidth: 920,
minHeight: 600,
title: 'codbash',
backgroundColor: '#0b0d12',
webPreferences: { contextIsolation: true, nodeIntegration: false, spellcheck: false },
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
spellcheck: false,
preload: path.join(__dirname, 'preload.js'),
},
});

// http/https links open in the user's real browser, not inside the app.
Expand Down
12 changes: 12 additions & 0 deletions desktop/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

// Minimal, safe bridge exposed to the loaded dashboard page. contextIsolation
// is on and nodeIntegration off, so the renderer only sees exactly what we
// expose here — a single method that opens the native folder picker.
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('codbashDesktop', {
isDesktop: true,
// Resolves to the chosen absolute folder path, or null if the user cancels.
pickFolder: () => ipcRenderer.invoke('codbash:pick-folder'),
});
17 changes: 17 additions & 0 deletions src/frontend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4085,9 +4085,26 @@ function openAddProject() {
var input = document.getElementById('apLocalPath');
if (input) { input.value = ''; setTimeout(function() { input.focus(); }, 50); }
var err = document.getElementById('apLocalError'); if (err) err.textContent = '';
// Desktop app only: reveal the native "Browse…" folder picker (the browser
// has no way to open a Finder folder dialog and return an absolute path).
var browse = document.getElementById('apBrowseBtn');
if (browse) browse.style.display =
(window.codbashDesktop && typeof window.codbashDesktop.pickFolder === 'function') ? '' : 'none';
_installModalFocusTrap(overlay);
}

// Open the OS folder picker (Finder) in the desktop app and drop the chosen
// absolute path into the input. No-op in the browser (button stays hidden).
function browseForProjectFolder() {
if (!(window.codbashDesktop && typeof window.codbashDesktop.pickFolder === 'function')) return;
window.codbashDesktop.pickFolder().then(function (dir) {
if (!dir) return;
var input = document.getElementById('apLocalPath');
if (input) { input.value = dir; input.focus(); }
var err = document.getElementById('apLocalError'); if (err) err.textContent = '';
}).catch(function () {});
}

function closeAddProject() {
var overlay = document.getElementById('addProjectOverlay');
if (overlay) {
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,12 @@ <h3 id="apTitle">Add project</h3>
<div class="ap-body">
<div class="ap-pane" id="apPaneLocal" role="tabpanel">
<label for="apLocalPath">Folder path on this machine</label>
<input type="text" id="apLocalPath" placeholder="/Users/me/code/my-project" autocomplete="off" aria-describedby="apLocalHint" list="apPathSuggestions" />
<div class="ap-path-row">
<input type="text" id="apLocalPath" placeholder="/Users/me/code/my-project" autocomplete="off" aria-describedby="apLocalHint" list="apPathSuggestions" />
<button type="button" id="apBrowseBtn" class="toolbar-btn" style="display:none" onclick="browseForProjectFolder()" title="Choose a folder…">Browse…</button>
</div>
<datalist id="apPathSuggestions"></datalist>
<div class="ap-hint" id="apLocalHint">Start typing to pick from your known project folders. <code>~</code> is expanded.</div>
<div class="ap-hint" id="apLocalHint">Start typing to pick from your known project folders, or <strong>Browse…</strong> in the desktop app. <code>~</code> is expanded.</div>
<div class="ap-error" id="apLocalError" role="alert"></div>
</div>
<div class="ap-pane" id="apPaneOwned" style="display:none" role="tabpanel">
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,9 @@ body[data-view="overview"] .toolbar { display: none; }
color: var(--text-primary);
font-size: 13px;
}
.ap-path-row { display: flex; gap: 8px; align-items: stretch; }
.ap-path-row input[type="text"] { flex: 1; }
.ap-path-row #apBrowseBtn { flex: 0 0 auto; white-space: nowrap; }
.ap-hint { font-size: 11px; color: var(--text-muted); margin-top: 6px; }
.ap-error { font-size: 12px; color: var(--accent-red); margin-top: 8px; min-height: 18px; }
.ap-filter { margin-bottom: 8px; }
Expand Down
Loading