Convert ES modules to plain scripts using window.NexusTerminal namespace
Replace ES module import/export with a global namespace pattern to avoid silent failures in the Wails v2 WKWebView which does not reliably support <script type="module"> tags. - Remove all import/export statements from JS files - Register each module's exports on window.NexusTerminal - Destructure dependencies from window.NexusTerminal at file top - Load all scripts in dependency order in index.html as plain <script> tags
This commit is contained in:
+17
-37
@@ -51,40 +51,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sidebar-nav" id="sidebar-nav">
|
<nav class="sidebar-nav" id="sidebar-nav">
|
||||||
<!-- Favourites -->
|
<!-- Dynamically managed by sidebar.js -->
|
||||||
<div class="sidebar-section" id="favourites-section">
|
<div id="sidebar-profiles" class="sidebar-profiles"></div>
|
||||||
<button class="sidebar-section-header" data-section="favourites">
|
|
||||||
<span class="section-icon">★</span>
|
|
||||||
<span>Favourites</span>
|
|
||||||
<span class="section-count" id="favourites-count">0</span>
|
|
||||||
</button>
|
|
||||||
<div class="sidebar-list" id="favourites-list">
|
|
||||||
<div class="sidebar-list-empty">No favourites yet</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Recent -->
|
|
||||||
<div class="sidebar-section" id="recent-section">
|
|
||||||
<button class="sidebar-section-header" data-section="recent">
|
|
||||||
<span class="section-icon">⟳</span>
|
|
||||||
<span>Recent</span>
|
|
||||||
</button>
|
|
||||||
<div class="sidebar-list" id="recent-list">
|
|
||||||
<div class="sidebar-list-empty">No recent sessions</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- All Connections -->
|
|
||||||
<div class="sidebar-section" id="all-section">
|
|
||||||
<button class="sidebar-section-header" data-section="all">
|
|
||||||
<span class="section-icon">⊞</span>
|
|
||||||
<span>All Connections</span>
|
|
||||||
<span class="section-count" id="all-count">0</span>
|
|
||||||
</button>
|
|
||||||
<div class="sidebar-list" id="all-list">
|
|
||||||
<div class="sidebar-list-empty">No saved connections</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
@@ -162,7 +130,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Session containers are injected here dynamically -->
|
<!-- Session panels are injected here dynamically by tabs.js -->
|
||||||
|
<div id="panel-container" class="panel-container"></div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- ============================================================
|
<!-- ============================================================
|
||||||
@@ -192,7 +161,18 @@
|
|||||||
<script src="/src/js/lib/xterm-addon-search.min.js"></script>
|
<script src="/src/js/lib/xterm-addon-search.min.js"></script>
|
||||||
<script src="/src/js/lib/xterm-addon-web-links.min.js"></script>
|
<script src="/src/js/lib/xterm-addon-web-links.min.js"></script>
|
||||||
|
|
||||||
<!-- Application entry point -->
|
<!-- Application scripts (loaded in dependency order) -->
|
||||||
<script type="module" src="/src/js/app.js"></script>
|
<script src="/src/js/utils.js"></script>
|
||||||
|
<script src="/src/js/state.js"></script>
|
||||||
|
<script src="/src/js/notifications.js"></script>
|
||||||
|
<script src="/src/js/sidebar.js"></script>
|
||||||
|
<script src="/src/js/tabs.js"></script>
|
||||||
|
<script src="/src/js/terminal.js"></script>
|
||||||
|
<script src="/src/js/profiles.js"></script>
|
||||||
|
<script src="/src/js/raw.js"></script>
|
||||||
|
<script src="/src/js/settings.js"></script>
|
||||||
|
<script src="/src/js/commands.js"></script>
|
||||||
|
<script src="/src/js/secrets.js"></script>
|
||||||
|
<script src="/src/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+43
-15
@@ -3,17 +3,7 @@
|
|||||||
* Initialises the application shell, loads profiles, and sets up event listeners.
|
* Initialises the application shell, loads profiles, and sets up event listeners.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { State } from './state.js';
|
const { State, Sidebar, TabManager, TerminalManager, ProfileEditor, RawViewer, Notifications, Settings, CommandPalette, SecretPrompt, stateLabel } = window.NexusTerminal;
|
||||||
import { Sidebar } from './sidebar.js';
|
|
||||||
import { TabManager } from './tabs.js';
|
|
||||||
import { TerminalManager } from './terminal.js';
|
|
||||||
import { ProfileEditor } from './profiles.js';
|
|
||||||
import { RawViewer } from './raw.js';
|
|
||||||
import { Notifications } from './notifications.js';
|
|
||||||
import { Settings } from './settings.js';
|
|
||||||
import { CommandPalette } from './commands.js';
|
|
||||||
import { SecretPrompt } from './secrets.js';
|
|
||||||
import { stateLabel } from './utils.js';
|
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -31,18 +21,51 @@ class App {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstraps the application after the DOM is ready.
|
* Bootstraps the application after the DOM is ready.
|
||||||
|
* UI event binding happens synchronously so buttons always work.
|
||||||
|
* Backend data loading waits for the Wails runtime to be available.
|
||||||
*/
|
*/
|
||||||
async init() {
|
async init() {
|
||||||
|
// Phase 1 — bind all UI event handlers immediately (DOM-only, no backend).
|
||||||
|
this.setupUI();
|
||||||
|
this.setupKeyboard();
|
||||||
|
this.updateStatusBar();
|
||||||
|
|
||||||
|
// Phase 2 — wait for the Wails runtime, then load backend data and bind events.
|
||||||
|
await this._waitForWailsRuntime();
|
||||||
|
this.setupEvents();
|
||||||
|
|
||||||
await this.loadProfiles();
|
await this.loadProfiles();
|
||||||
await this.loadPreferences();
|
await this.loadPreferences();
|
||||||
await this.checkSecretStore();
|
await this.checkSecretStore();
|
||||||
|
|
||||||
this.setupEvents();
|
|
||||||
this.setupKeyboard();
|
|
||||||
this.setupUI();
|
|
||||||
this.updateStatusBar();
|
this.updateStatusBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls for `window.go` and `window.runtime` to become available.
|
||||||
|
* Wails injects these into the webview; on fast machines they are
|
||||||
|
* present before DOMContentLoaded, but on slower starts there may
|
||||||
|
* be a brief delay.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_waitForWailsRuntime() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const maxWait = 5000;
|
||||||
|
const start = Date.now();
|
||||||
|
const check = () => {
|
||||||
|
if (window.go && window.runtime) {
|
||||||
|
resolve();
|
||||||
|
} else if (Date.now() - start < maxWait) {
|
||||||
|
setTimeout(check, 50);
|
||||||
|
} else {
|
||||||
|
console.warn('Wails runtime not detected after ' + maxWait + 'ms — proceeding anyway');
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ── Data loading ─────────────────────────────────────────────────
|
// ── Data loading ─────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,8 +117,13 @@ class App {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers listeners for backend events emitted via the Wails runtime.
|
* Registers listeners for backend events emitted via the Wails runtime.
|
||||||
|
* Guarded: silently skips if the runtime is unavailable.
|
||||||
*/
|
*/
|
||||||
setupEvents() {
|
setupEvents() {
|
||||||
|
if (!window.runtime || typeof window.runtime.EventsOn !== 'function') {
|
||||||
|
console.warn('window.runtime not available — Wails event listeners not bound');
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Incoming session data (terminal output)
|
// Incoming session data (terminal output)
|
||||||
window.runtime.EventsOn('session:data', (payload) => {
|
window.runtime.EventsOn('session:data', (payload) => {
|
||||||
this.terminals.handleData(payload.sessionID, payload.data);
|
this.terminals.handleData(payload.sessionID, payload.data);
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
* profile connections, and utility actions.
|
* profile connections, and utility actions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml, fuzzyMatch, $ } from './utils.js';
|
const { createElement, escapeHtml, fuzzyMatch, $ } = window.NexusTerminal;
|
||||||
|
|
||||||
export class CommandPalette {
|
class CommandPalette {
|
||||||
/**
|
/**
|
||||||
* @param {import('./app.js').App} app
|
* @param {import('./app.js').App} app
|
||||||
*/
|
*/
|
||||||
@@ -379,3 +379,6 @@ export class CommandPalette {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.CommandPalette = CommandPalette;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* in the top-right corner of the viewport.
|
* in the top-right corner of the viewport.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml } from './utils.js';
|
const { createElement, escapeHtml } = window.NexusTerminal;
|
||||||
|
|
||||||
/** Map of notification type to SVG icon path data. */
|
/** Map of notification type to SVG icon path data. */
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
@@ -14,7 +14,7 @@ const ICONS = {
|
|||||||
info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Notifications {
|
class Notifications {
|
||||||
constructor() {
|
constructor() {
|
||||||
/** @type {HTMLElement} */
|
/** @type {HTMLElement} */
|
||||||
this.container = document.getElementById('notifications');
|
this.container = document.getElementById('notifications');
|
||||||
@@ -122,3 +122,6 @@ export class Notifications {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.Notifications = Notifications;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Dynamically switches form sections based on the selected protocol.
|
* Dynamically switches form sections based on the selected protocol.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml, generateId, $ } from './utils.js';
|
const { createElement, escapeHtml, generateId, $ } = window.NexusTerminal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default values mirroring the Go backend defaults.
|
* Default values mirroring the Go backend defaults.
|
||||||
@@ -53,7 +53,7 @@ const DEFAULTS = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ProfileEditor {
|
class ProfileEditor {
|
||||||
/**
|
/**
|
||||||
* @param {import('./app.js').App} app
|
* @param {import('./app.js').App} app
|
||||||
*/
|
*/
|
||||||
@@ -886,3 +886,6 @@ export class ProfileEditor {
|
|||||||
return Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join('\n');
|
return Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.ProfileEditor = ProfileEditor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* along with a send bar, pause/resume, and capture saving.
|
* along with a send bar, pause/resume, and capture saving.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml, formatTimestamp, formatBytes } from './utils.js';
|
const { createElement, escapeHtml, formatTimestamp, formatBytes } = window.NexusTerminal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of data entries retained in the scrollback buffer.
|
* Maximum number of data entries retained in the scrollback buffer.
|
||||||
@@ -12,7 +12,7 @@ import { createElement, escapeHtml, formatTimestamp, formatBytes } from './utils
|
|||||||
*/
|
*/
|
||||||
const MAX_BUFFER_ENTRIES = 5000;
|
const MAX_BUFFER_ENTRIES = 5000;
|
||||||
|
|
||||||
export class RawViewer {
|
class RawViewer {
|
||||||
/**
|
/**
|
||||||
* @param {import('./app.js').App} app
|
* @param {import('./app.js').App} app
|
||||||
*/
|
*/
|
||||||
@@ -562,3 +562,6 @@ export class RawViewer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.RawViewer = RawViewer;
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
* and SSH host-key verification confirmations.
|
* and SSH host-key verification confirmations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml } from './utils.js';
|
const { createElement, escapeHtml } = window.NexusTerminal;
|
||||||
|
|
||||||
export class SecretPrompt {
|
class SecretPrompt {
|
||||||
constructor(app) {
|
constructor(app) {
|
||||||
/** @type {import('./app.js').App} */
|
/** @type {import('./app.js').App} */
|
||||||
this.app = app;
|
this.app = app;
|
||||||
@@ -298,3 +298,6 @@ export class SecretPrompt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.SecretPrompt = SecretPrompt;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* terminal defaults, security, known-hosts management, and about info.
|
* terminal defaults, security, known-hosts management, and about info.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml, formatTimestamp, $ } from './utils.js';
|
const { createElement, escapeHtml, formatTimestamp, $ } = window.NexusTerminal;
|
||||||
|
|
||||||
/** Settings tab definitions. */
|
/** Settings tab definitions. */
|
||||||
const TABS = [
|
const TABS = [
|
||||||
@@ -15,7 +15,7 @@ const TABS = [
|
|||||||
{ key: 'about', label: 'About' },
|
{ key: 'about', label: 'About' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export class Settings {
|
class Settings {
|
||||||
/**
|
/**
|
||||||
* @param {import('./app.js').App} app
|
* @param {import('./app.js').App} app
|
||||||
*/
|
*/
|
||||||
@@ -461,3 +461,6 @@ export class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.Settings = Settings;
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
* and context menus.
|
* and context menus.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml, fuzzyMatch, protocolColour, dismissContextMenus, truncate } from './utils.js';
|
const { createElement, escapeHtml, fuzzyMatch, protocolColour, dismissContextMenus, truncate } = window.NexusTerminal;
|
||||||
|
|
||||||
/** Maximum number of profiles shown in the "Recent" section. */
|
/** Maximum number of profiles shown in the "Recent" section. */
|
||||||
const RECENT_LIMIT = 5;
|
const RECENT_LIMIT = 5;
|
||||||
|
|
||||||
export class Sidebar {
|
class Sidebar {
|
||||||
/**
|
/**
|
||||||
* @param {import('./app.js').App} app
|
* @param {import('./app.js').App} app
|
||||||
*/
|
*/
|
||||||
@@ -333,3 +333,6 @@ export class Sidebar {
|
|||||||
.map((r) => r.profile);
|
.map((r) => r.profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.Sidebar = Sidebar;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* emitting change events so the UI stays in sync.
|
* emitting change events so the UI stays in sync.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class State {
|
class State {
|
||||||
constructor() {
|
constructor() {
|
||||||
/** @type {Array<Object>} */
|
/** @type {Array<Object>} */
|
||||||
this.profiles = [];
|
this.profiles = [];
|
||||||
@@ -223,3 +223,6 @@ export class State {
|
|||||||
this.emit('secretStore:init', initialised);
|
this.emit('secretStore:init', initialised);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.State = State;
|
||||||
|
|||||||
+24
-18
@@ -4,16 +4,19 @@
|
|||||||
* pinning tabs, and context menus.
|
* pinning tabs, and context menus.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createElement, escapeHtml, protocolColour, stateLabel, dismissContextMenus } from './utils.js';
|
const { createElement, escapeHtml, protocolColour, stateLabel, dismissContextMenus } = window.NexusTerminal;
|
||||||
|
|
||||||
export class TabManager {
|
class TabManager {
|
||||||
/**
|
/**
|
||||||
* @param {import('./app.js').App} app
|
* @param {import('./app.js').App} app
|
||||||
*/
|
*/
|
||||||
constructor(app) {
|
constructor(app) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
|
||||||
/** @type {HTMLElement} The tab bar container. */
|
/** @type {HTMLElement} The inner tab list where tab elements are inserted. */
|
||||||
|
this.tabList = document.getElementById('tab-list');
|
||||||
|
|
||||||
|
/** @type {HTMLElement} The outer tab bar wrapper (used for drag-and-drop). */
|
||||||
this.tabBar = document.getElementById('tab-bar');
|
this.tabBar = document.getElementById('tab-bar');
|
||||||
|
|
||||||
/** @type {HTMLElement} The container for terminal/viewer panels. */
|
/** @type {HTMLElement} The container for terminal/viewer panels. */
|
||||||
@@ -56,7 +59,7 @@ export class TabManager {
|
|||||||
if (idx !== -1) this.order.splice(idx, 1);
|
if (idx !== -1) this.order.splice(idx, 1);
|
||||||
|
|
||||||
// Remove tab element
|
// Remove tab element
|
||||||
const tabEl = this.tabBar.querySelector(`[data-session-id="${sessionId}"]`);
|
const tabEl = this.tabList.querySelector(`[data-session-id="${sessionId}"]`);
|
||||||
if (tabEl) tabEl.remove();
|
if (tabEl) tabEl.remove();
|
||||||
|
|
||||||
// Remove panel
|
// Remove panel
|
||||||
@@ -86,7 +89,7 @@ export class TabManager {
|
|||||||
this.app.state.setActiveSession(sessionId);
|
this.app.state.setActiveSession(sessionId);
|
||||||
|
|
||||||
// Highlight active tab
|
// Highlight active tab
|
||||||
this.tabBar.querySelectorAll('.tab').forEach((t) => {
|
this.tabList.querySelectorAll('.tab').forEach((t) => {
|
||||||
t.classList.toggle('tab-active', t.dataset.sessionId === sessionId);
|
t.classList.toggle('tab-active', t.dataset.sessionId === sessionId);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,7 +149,7 @@ export class TabManager {
|
|||||||
*/
|
*/
|
||||||
renameTab(sessionId, name) {
|
renameTab(sessionId, name) {
|
||||||
this.app.state.updateSession(sessionId, { tabName: name });
|
this.app.state.updateSession(sessionId, { tabName: name });
|
||||||
const tabEl = this.tabBar.querySelector(`[data-session-id="${sessionId}"]`);
|
const tabEl = this.tabList.querySelector(`[data-session-id="${sessionId}"]`);
|
||||||
if (tabEl) {
|
if (tabEl) {
|
||||||
tabEl.querySelector('.tab-name').textContent = name;
|
tabEl.querySelector('.tab-name').textContent = name;
|
||||||
}
|
}
|
||||||
@@ -216,7 +219,7 @@ export class TabManager {
|
|||||||
updateSessionState(sessionId, state, error) {
|
updateSessionState(sessionId, state, error) {
|
||||||
this.app.state.updateSession(sessionId, { state, error: error || '' });
|
this.app.state.updateSession(sessionId, { state, error: error || '' });
|
||||||
|
|
||||||
const tabEl = this.tabBar.querySelector(`[data-session-id="${sessionId}"]`);
|
const tabEl = this.tabList.querySelector(`[data-session-id="${sessionId}"]`);
|
||||||
if (!tabEl) return;
|
if (!tabEl) return;
|
||||||
|
|
||||||
const dot = tabEl.querySelector('.tab-status');
|
const dot = tabEl.querySelector('.tab-status');
|
||||||
@@ -383,7 +386,7 @@ export class TabManager {
|
|||||||
this.showContextMenu(sessionInfo.id, e.clientX, e.clientY);
|
this.showContextMenu(sessionInfo.id, e.clientX, e.clientY);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabBar.appendChild(tab);
|
this.tabList.appendChild(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Internal helpers ─────────────────────────────────────────────
|
// ── Internal helpers ─────────────────────────────────────────────
|
||||||
@@ -405,8 +408,8 @@ export class TabManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const id of sorted) {
|
for (const id of sorted) {
|
||||||
const el = this.tabBar.querySelector(`[data-session-id="${id}"]`);
|
const el = this.tabList.querySelector(`[data-session-id="${id}"]`);
|
||||||
if (el) this.tabBar.appendChild(el);
|
if (el) this.tabList.appendChild(el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +420,7 @@ export class TabManager {
|
|||||||
*/
|
*/
|
||||||
_updateTabStyles(sessionId) {
|
_updateTabStyles(sessionId) {
|
||||||
const session = this.app.state.getSession(sessionId);
|
const session = this.app.state.getSession(sessionId);
|
||||||
const tabEl = this.tabBar.querySelector(`[data-session-id="${sessionId}"]`);
|
const tabEl = this.tabList.querySelector(`[data-session-id="${sessionId}"]`);
|
||||||
if (tabEl && session) {
|
if (tabEl && session) {
|
||||||
tabEl.classList.toggle('tab-pinned', !!session.pinned);
|
tabEl.classList.toggle('tab-pinned', !!session.pinned);
|
||||||
}
|
}
|
||||||
@@ -484,11 +487,11 @@ export class TabManager {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_setupDragAndDrop() {
|
_setupDragAndDrop() {
|
||||||
if (!this.tabBar) return;
|
if (!this.tabList) return;
|
||||||
|
|
||||||
let draggedId = null;
|
let draggedId = null;
|
||||||
|
|
||||||
this.tabBar.addEventListener('dragstart', (e) => {
|
this.tabList.addEventListener('dragstart', (e) => {
|
||||||
const tab = e.target.closest('.tab');
|
const tab = e.target.closest('.tab');
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
draggedId = tab.dataset.sessionId;
|
draggedId = tab.dataset.sessionId;
|
||||||
@@ -496,7 +499,7 @@ export class TabManager {
|
|||||||
e.dataTransfer.effectAllowed = 'move';
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabBar.addEventListener('dragover', (e) => {
|
this.tabList.addEventListener('dragover', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.dataTransfer.dropEffect = 'move';
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
|
||||||
@@ -509,14 +512,14 @@ export class TabManager {
|
|||||||
target.classList.toggle('tab-drop-after', e.clientX >= midX);
|
target.classList.toggle('tab-drop-after', e.clientX >= midX);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabBar.addEventListener('dragleave', (e) => {
|
this.tabList.addEventListener('dragleave', (e) => {
|
||||||
const target = e.target.closest('.tab');
|
const target = e.target.closest('.tab');
|
||||||
if (target) {
|
if (target) {
|
||||||
target.classList.remove('tab-drop-before', 'tab-drop-after');
|
target.classList.remove('tab-drop-before', 'tab-drop-after');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabBar.addEventListener('drop', (e) => {
|
this.tabList.addEventListener('drop', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const target = e.target.closest('.tab');
|
const target = e.target.closest('.tab');
|
||||||
if (!target || !draggedId) return;
|
if (!target || !draggedId) return;
|
||||||
@@ -536,11 +539,14 @@ export class TabManager {
|
|||||||
this._reorderDOM();
|
this._reorderDOM();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabBar.addEventListener('dragend', () => {
|
this.tabList.addEventListener('dragend', () => {
|
||||||
this.tabBar.querySelectorAll('.tab').forEach((t) => {
|
this.tabList.querySelectorAll('.tab').forEach((t) => {
|
||||||
t.classList.remove('tab-dragging', 'tab-drop-before', 'tab-drop-after');
|
t.classList.remove('tab-dragging', 'tab-drop-before', 'tab-drop-after');
|
||||||
});
|
});
|
||||||
draggedId = null;
|
draggedId = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.TabManager = TabManager;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
/* global Terminal, FitAddon, SearchAddon, WebLinksAddon */
|
/* global Terminal, FitAddon, SearchAddon, WebLinksAddon */
|
||||||
|
|
||||||
export class TerminalManager {
|
class TerminalManager {
|
||||||
/**
|
/**
|
||||||
* @param {import('./app.js').App} app
|
* @param {import('./app.js').App} app
|
||||||
*/
|
*/
|
||||||
@@ -284,3 +284,6 @@ export class TerminalManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.TerminalManager = TerminalManager;
|
||||||
|
|||||||
+18
-15
@@ -8,7 +8,7 @@
|
|||||||
* @param {string} selector - CSS selector.
|
* @param {string} selector - CSS selector.
|
||||||
* @returns {Element|null}
|
* @returns {Element|null}
|
||||||
*/
|
*/
|
||||||
export function $(selector) {
|
function $(selector) {
|
||||||
return document.querySelector(selector);
|
return document.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export function $(selector) {
|
|||||||
* @param {string} selector - CSS selector.
|
* @param {string} selector - CSS selector.
|
||||||
* @returns {NodeListOf<Element>}
|
* @returns {NodeListOf<Element>}
|
||||||
*/
|
*/
|
||||||
export function $$(selector) {
|
function $$(selector) {
|
||||||
return document.querySelectorAll(selector);
|
return document.querySelectorAll(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export function $$(selector) {
|
|||||||
* @param {Object} [attrs] - Attribute key/value pairs.
|
* @param {Object} [attrs] - Attribute key/value pairs.
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
export function createElement(tag, className, attrs = {}) {
|
function createElement(tag, className, attrs = {}) {
|
||||||
const el = document.createElement(tag);
|
const el = document.createElement(tag);
|
||||||
if (className) {
|
if (className) {
|
||||||
el.className = className;
|
el.className = className;
|
||||||
@@ -56,7 +56,7 @@ export function createElement(tag, className, attrs = {}) {
|
|||||||
* @param {string} str - Raw string.
|
* @param {string} str - Raw string.
|
||||||
* @returns {string} Escaped string safe for innerHTML.
|
* @returns {string} Escaped string safe for innerHTML.
|
||||||
*/
|
*/
|
||||||
export function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
const map = {
|
const map = {
|
||||||
'&': '&',
|
'&': '&',
|
||||||
@@ -73,7 +73,7 @@ export function escapeHtml(str) {
|
|||||||
* @param {Date|string} date
|
* @param {Date|string} date
|
||||||
* @returns {string} e.g. "14:32:07" or "2 Jan 14:32".
|
* @returns {string} e.g. "14:32:07" or "2 Jan 14:32".
|
||||||
*/
|
*/
|
||||||
export function formatTimestamp(date) {
|
function formatTimestamp(date) {
|
||||||
if (!date) return '';
|
if (!date) return '';
|
||||||
const d = date instanceof Date ? date : new Date(date);
|
const d = date instanceof Date ? date : new Date(date);
|
||||||
if (isNaN(d.getTime())) return '';
|
if (isNaN(d.getTime())) return '';
|
||||||
@@ -98,7 +98,7 @@ export function formatTimestamp(date) {
|
|||||||
* @param {number} ms - Delay in milliseconds.
|
* @param {number} ms - Delay in milliseconds.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function debounce(fn, ms) {
|
function debounce(fn, ms) {
|
||||||
let timer = null;
|
let timer = null;
|
||||||
return function (...args) {
|
return function (...args) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
@@ -113,7 +113,7 @@ export function debounce(fn, ms) {
|
|||||||
* @param {string} query
|
* @param {string} query
|
||||||
* @returns {{ match: boolean, score: number }}
|
* @returns {{ match: boolean, score: number }}
|
||||||
*/
|
*/
|
||||||
export function fuzzyMatch(text, query) {
|
function fuzzyMatch(text, query) {
|
||||||
if (!query) return { match: true, score: 1 };
|
if (!query) return { match: true, score: 1 };
|
||||||
const lowerText = text.toLowerCase();
|
const lowerText = text.toLowerCase();
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
@@ -146,7 +146,7 @@ export function fuzzyMatch(text, query) {
|
|||||||
* @param {string} str
|
* @param {string} str
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function base64Encode(str) {
|
function base64Encode(str) {
|
||||||
return btoa(str);
|
return btoa(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export function base64Encode(str) {
|
|||||||
* @param {string} str
|
* @param {string} str
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function base64Decode(str) {
|
function base64Decode(str) {
|
||||||
return atob(str);
|
return atob(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ export function base64Decode(str) {
|
|||||||
* Generates a new UUID v4 using the browser crypto API.
|
* Generates a new UUID v4 using the browser crypto API.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function generateId() {
|
function generateId() {
|
||||||
return crypto.randomUUID();
|
return crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ export function generateId() {
|
|||||||
* @param {number} bytes
|
* @param {number} bytes
|
||||||
* @returns {string} e.g. "1.23 KB"
|
* @returns {string} e.g. "1.23 KB"
|
||||||
*/
|
*/
|
||||||
export function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
@@ -187,7 +187,7 @@ export function formatBytes(bytes) {
|
|||||||
* @param {number} len
|
* @param {number} len
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function truncate(str, len) {
|
function truncate(str, len) {
|
||||||
if (!str || str.length <= len) return str || '';
|
if (!str || str.length <= len) return str || '';
|
||||||
return str.slice(0, len) + '…';
|
return str.slice(0, len) + '…';
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ export function truncate(str, len) {
|
|||||||
/**
|
/**
|
||||||
* Removes any open context menus from the DOM.
|
* Removes any open context menus from the DOM.
|
||||||
*/
|
*/
|
||||||
export function dismissContextMenus() {
|
function dismissContextMenus() {
|
||||||
document.querySelectorAll('.context-menu').forEach((m) => m.remove());
|
document.querySelectorAll('.context-menu').forEach((m) => m.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ export function dismissContextMenus() {
|
|||||||
* @param {string} protocol - 'ssh', 'telnet', or 'raw'.
|
* @param {string} protocol - 'ssh', 'telnet', or 'raw'.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function protocolColour(protocol) {
|
function protocolColour(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case 'ssh': return 'protocol-ssh';
|
case 'ssh': return 'protocol-ssh';
|
||||||
case 'telnet': return 'protocol-telnet';
|
case 'telnet': return 'protocol-telnet';
|
||||||
@@ -218,7 +218,7 @@ export function protocolColour(protocol) {
|
|||||||
* @param {string} state
|
* @param {string} state
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function stateLabel(state) {
|
function stateLabel(state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'connecting': return 'Connecting';
|
case 'connecting': return 'Connecting';
|
||||||
case 'connected': return 'Connected';
|
case 'connected': return 'Connected';
|
||||||
@@ -227,3 +227,6 @@ export function stateLabel(state) {
|
|||||||
default: return state || 'Unknown';
|
default: return state || 'Unknown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
Object.assign(window.NexusTerminal, { $, $$, createElement, escapeHtml, formatTimestamp, debounce, fuzzyMatch, base64Encode, base64Decode, generateId, formatBytes, truncate, dismissContextMenus, protocolColour, stateLabel });
|
||||||
|
|||||||
Reference in New Issue
Block a user