@cluesmith/codev 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/generate-image.js +7 -0
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +0 -36
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/start.js +32 -0
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.d.ts +0 -2
- package/dist/agent-farm/servers/dashboard-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +89 -4
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/servers/open-server.d.ts +0 -2
- package/dist/agent-farm/servers/open-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/open-server.js +51 -7
- package/dist/agent-farm/servers/open-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.d.ts +0 -2
- package/dist/agent-farm/servers/tower-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +16 -4
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/utils/shell.d.ts +2 -1
- package/dist/agent-farm/utils/shell.d.ts.map +1 -1
- package/dist/agent-farm/utils/shell.js +35 -2
- package/dist/agent-farm/utils/shell.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/generate-image.d.ts +13 -0
- package/dist/commands/generate-image.d.ts.map +1 -0
- package/dist/commands/generate-image.js +155 -0
- package/dist/commands/generate-image.js.map +1 -0
- package/package.json +5 -3
- package/templates/dashboard-split.html +378 -3
- package/templates/open.html +48 -1
|
@@ -1156,6 +1156,138 @@
|
|
|
1156
1156
|
.tab.tab-uncloseable .close {
|
|
1157
1157
|
display: none;
|
|
1158
1158
|
}
|
|
1159
|
+
|
|
1160
|
+
/* Files Tab Styles (Spec 0055) */
|
|
1161
|
+
.files-container {
|
|
1162
|
+
flex: 1;
|
|
1163
|
+
overflow-y: auto;
|
|
1164
|
+
display: flex;
|
|
1165
|
+
flex-direction: column;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.files-header {
|
|
1169
|
+
display: flex;
|
|
1170
|
+
justify-content: space-between;
|
|
1171
|
+
align-items: center;
|
|
1172
|
+
padding: 8px 12px;
|
|
1173
|
+
background: var(--bg-secondary);
|
|
1174
|
+
border-bottom: 1px solid var(--border);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.files-header-title {
|
|
1178
|
+
font-size: 12px;
|
|
1179
|
+
color: var(--text-muted);
|
|
1180
|
+
text-transform: uppercase;
|
|
1181
|
+
letter-spacing: 0.5px;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.files-header-actions {
|
|
1185
|
+
display: flex;
|
|
1186
|
+
gap: 4px;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.files-header-actions button {
|
|
1190
|
+
padding: 4px 8px;
|
|
1191
|
+
border-radius: 4px;
|
|
1192
|
+
border: 1px solid var(--border);
|
|
1193
|
+
background: var(--bg-tertiary);
|
|
1194
|
+
color: var(--text-secondary);
|
|
1195
|
+
cursor: pointer;
|
|
1196
|
+
font-size: 11px;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
.files-header-actions button:hover {
|
|
1200
|
+
background: var(--tab-hover);
|
|
1201
|
+
color: var(--text-primary);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.files-tree {
|
|
1205
|
+
flex: 1;
|
|
1206
|
+
overflow-y: auto;
|
|
1207
|
+
padding: 8px 0;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
.tree-item {
|
|
1211
|
+
display: flex;
|
|
1212
|
+
align-items: center;
|
|
1213
|
+
padding: 4px 8px;
|
|
1214
|
+
cursor: pointer;
|
|
1215
|
+
user-select: none;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.tree-item:hover {
|
|
1219
|
+
background: var(--bg-secondary);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
.tree-item.selected {
|
|
1223
|
+
background: var(--tab-active);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
.tree-item-icon {
|
|
1227
|
+
width: 16px;
|
|
1228
|
+
height: 16px;
|
|
1229
|
+
margin-right: 4px;
|
|
1230
|
+
display: flex;
|
|
1231
|
+
align-items: center;
|
|
1232
|
+
justify-content: center;
|
|
1233
|
+
font-size: 10px;
|
|
1234
|
+
color: var(--text-muted);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.tree-item-icon.folder-toggle {
|
|
1238
|
+
cursor: pointer;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
.tree-item-icon.folder-toggle:hover {
|
|
1242
|
+
color: var(--text-secondary);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.tree-item-name {
|
|
1246
|
+
font-size: 13px;
|
|
1247
|
+
color: var(--text-secondary);
|
|
1248
|
+
overflow: hidden;
|
|
1249
|
+
text-overflow: ellipsis;
|
|
1250
|
+
white-space: nowrap;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.tree-item:hover .tree-item-name {
|
|
1254
|
+
color: var(--text-primary);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.tree-item[data-type="dir"] .tree-item-name {
|
|
1258
|
+
color: var(--text-primary);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.tree-item[data-type="file"]:hover .tree-item-name {
|
|
1262
|
+
color: var(--accent);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.tree-children {
|
|
1266
|
+
overflow: hidden;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
.tree-children.collapsed {
|
|
1270
|
+
display: none;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
.files-loading {
|
|
1274
|
+
display: flex;
|
|
1275
|
+
align-items: center;
|
|
1276
|
+
justify-content: center;
|
|
1277
|
+
padding: 24px;
|
|
1278
|
+
color: var(--text-muted);
|
|
1279
|
+
font-size: 13px;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.files-error {
|
|
1283
|
+
padding: 16px;
|
|
1284
|
+
margin: 8px;
|
|
1285
|
+
background: rgba(239, 68, 68, 0.1);
|
|
1286
|
+
border: 1px solid var(--status-error);
|
|
1287
|
+
border-radius: 6px;
|
|
1288
|
+
color: var(--text-secondary);
|
|
1289
|
+
font-size: 13px;
|
|
1290
|
+
}
|
|
1159
1291
|
</style>
|
|
1160
1292
|
</head>
|
|
1161
1293
|
<body>
|
|
@@ -1183,7 +1315,6 @@
|
|
|
1183
1315
|
</button>
|
|
1184
1316
|
<div class="overflow-menu hidden" id="overflow-menu" role="menu"></div>
|
|
1185
1317
|
<div class="add-buttons">
|
|
1186
|
-
<button class="add-btn" onclick="showFileDialog()" title="Open file">+ 📄</button>
|
|
1187
1318
|
<button class="add-btn" onclick="spawnBuilder()" title="Spawn worktree builder">+ 🔨</button>
|
|
1188
1319
|
<button class="add-btn" onclick="spawnShell()" title="New shell">+ >_</button>
|
|
1189
1320
|
</div>
|
|
@@ -1374,6 +1505,12 @@
|
|
|
1374
1505
|
let projectlistError = null;
|
|
1375
1506
|
let projectlistDebounce = null;
|
|
1376
1507
|
|
|
1508
|
+
// Files tab state (Spec 0055)
|
|
1509
|
+
let filesTreeData = [];
|
|
1510
|
+
let filesTreeExpanded = new Set(); // Set of expanded folder paths
|
|
1511
|
+
let filesTreeError = null;
|
|
1512
|
+
let filesTreeLoaded = false;
|
|
1513
|
+
|
|
1377
1514
|
// Build tabs from initial state
|
|
1378
1515
|
function buildTabsFromState() {
|
|
1379
1516
|
const previousTabIds = new Set(tabs.map(t => t.id));
|
|
@@ -1387,6 +1524,14 @@
|
|
|
1387
1524
|
closeable: false
|
|
1388
1525
|
});
|
|
1389
1526
|
|
|
1527
|
+
// Files tab is second and uncloseable (Spec 0055)
|
|
1528
|
+
tabs.push({
|
|
1529
|
+
id: 'files',
|
|
1530
|
+
type: 'files',
|
|
1531
|
+
name: 'Files',
|
|
1532
|
+
closeable: false
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1390
1535
|
// Add file tabs from annotations
|
|
1391
1536
|
for (const annotation of state.annotations || []) {
|
|
1392
1537
|
tabs.push({
|
|
@@ -1424,7 +1569,7 @@
|
|
|
1424
1569
|
|
|
1425
1570
|
// Detect new tabs and auto-switch to them (skip projects tab)
|
|
1426
1571
|
for (const tab of tabs) {
|
|
1427
|
-
if (tab.id !== 'projects' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
|
|
1572
|
+
if (tab.id !== 'projects' && tab.id !== 'files' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
|
|
1428
1573
|
// This is a new tab - switch to it
|
|
1429
1574
|
activeTabId = tab.id;
|
|
1430
1575
|
break;
|
|
@@ -1519,6 +1664,7 @@
|
|
|
1519
1664
|
function getTabIcon(type) {
|
|
1520
1665
|
switch (type) {
|
|
1521
1666
|
case 'projects': return '📋';
|
|
1667
|
+
case 'files': return '📁';
|
|
1522
1668
|
case 'file': return '📄';
|
|
1523
1669
|
case 'builder': return '🔨';
|
|
1524
1670
|
case 'shell': return '>_';
|
|
@@ -1626,6 +1772,16 @@
|
|
|
1626
1772
|
return;
|
|
1627
1773
|
}
|
|
1628
1774
|
|
|
1775
|
+
// Handle files tab specially (no iframe, inline content)
|
|
1776
|
+
if (tab.type === 'files') {
|
|
1777
|
+
if (currentTabType !== 'files') {
|
|
1778
|
+
currentTabType = 'files';
|
|
1779
|
+
currentTabPort = null;
|
|
1780
|
+
renderFilesTab();
|
|
1781
|
+
}
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1629
1785
|
// For other tabs, only update iframe if port changed (avoid flashing on poll)
|
|
1630
1786
|
if (currentTabPort !== tab.port || currentTabType !== tab.type) {
|
|
1631
1787
|
currentTabPort = tab.port;
|
|
@@ -2810,6 +2966,225 @@
|
|
|
2810
2966
|
`;
|
|
2811
2967
|
}
|
|
2812
2968
|
|
|
2969
|
+
// ========================================
|
|
2970
|
+
// Files Tab Functions (Spec 0055)
|
|
2971
|
+
// ========================================
|
|
2972
|
+
|
|
2973
|
+
// Load the file tree from the API
|
|
2974
|
+
async function loadFilesTree() {
|
|
2975
|
+
try {
|
|
2976
|
+
const response = await fetch('/api/files');
|
|
2977
|
+
if (!response.ok) {
|
|
2978
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2979
|
+
}
|
|
2980
|
+
filesTreeData = await response.json();
|
|
2981
|
+
filesTreeError = null;
|
|
2982
|
+
filesTreeLoaded = true;
|
|
2983
|
+
} catch (err) {
|
|
2984
|
+
console.error('Failed to load files tree:', err);
|
|
2985
|
+
filesTreeError = 'Could not load file tree: ' + err.message;
|
|
2986
|
+
filesTreeData = [];
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
// Render the files tab (entry point)
|
|
2991
|
+
// Only fetches on first load; use refreshFilesTree() to force reload
|
|
2992
|
+
async function renderFilesTab() {
|
|
2993
|
+
const content = document.getElementById('tab-content');
|
|
2994
|
+
|
|
2995
|
+
// If already loaded, just render cached data (no network request)
|
|
2996
|
+
if (filesTreeLoaded) {
|
|
2997
|
+
renderFilesTabContent();
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
// First load - show loading state and fetch
|
|
3002
|
+
content.innerHTML = '<div class="files-loading">Loading files...</div>';
|
|
3003
|
+
await loadFilesTree();
|
|
3004
|
+
renderFilesTabContent();
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// Render the files tab content (internal - called after data is loaded)
|
|
3008
|
+
function renderFilesTabContent() {
|
|
3009
|
+
const content = document.getElementById('tab-content');
|
|
3010
|
+
|
|
3011
|
+
if (filesTreeError) {
|
|
3012
|
+
content.innerHTML = `
|
|
3013
|
+
<div class="files-container">
|
|
3014
|
+
<div class="files-error">${escapeHtml(filesTreeError)}</div>
|
|
3015
|
+
</div>
|
|
3016
|
+
`;
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
content.innerHTML = `
|
|
3021
|
+
<div class="files-container">
|
|
3022
|
+
<div class="files-header">
|
|
3023
|
+
<span class="files-header-title">Explorer</span>
|
|
3024
|
+
<div class="files-header-actions">
|
|
3025
|
+
<button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
|
|
3026
|
+
<button onclick="expandAllFolders()" title="Expand All">⊞</button>
|
|
3027
|
+
<button onclick="refreshFilesTree()" title="Refresh">↻</button>
|
|
3028
|
+
</div>
|
|
3029
|
+
</div>
|
|
3030
|
+
<div class="files-tree" id="files-tree">
|
|
3031
|
+
${renderTreeNodes(filesTreeData, 0)}
|
|
3032
|
+
</div>
|
|
3033
|
+
</div>
|
|
3034
|
+
`;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
// Escape a string for use inside a JavaScript string literal in onclick handlers
|
|
3038
|
+
// This handles quotes, backslashes, and other special characters
|
|
3039
|
+
function escapeJsString(str) {
|
|
3040
|
+
return str
|
|
3041
|
+
.replace(/\\/g, '\\\\')
|
|
3042
|
+
.replace(/'/g, "\\'")
|
|
3043
|
+
.replace(/"/g, '\\"')
|
|
3044
|
+
.replace(/\n/g, '\\n')
|
|
3045
|
+
.replace(/\r/g, '\\r');
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// Render tree nodes recursively
|
|
3049
|
+
function renderTreeNodes(nodes, depth) {
|
|
3050
|
+
if (!nodes || nodes.length === 0) return '';
|
|
3051
|
+
|
|
3052
|
+
return nodes.map(node => {
|
|
3053
|
+
const indent = depth * 16;
|
|
3054
|
+
const isExpanded = filesTreeExpanded.has(node.path);
|
|
3055
|
+
// Use escapeJsString for onclick handlers (handles quotes correctly)
|
|
3056
|
+
// Use escapeHtml for data attributes and display text (handles XSS)
|
|
3057
|
+
const jsPath = escapeJsString(node.path);
|
|
3058
|
+
|
|
3059
|
+
if (node.type === 'dir') {
|
|
3060
|
+
const icon = isExpanded ? '▼' : '▶';
|
|
3061
|
+
const childrenHtml = node.children && node.children.length > 0
|
|
3062
|
+
? `<div class="tree-children ${isExpanded ? '' : 'collapsed'}" data-path="${escapeHtml(node.path)}">${renderTreeNodes(node.children, depth + 1)}</div>`
|
|
3063
|
+
: '';
|
|
3064
|
+
|
|
3065
|
+
return `
|
|
3066
|
+
<div class="tree-item" data-type="dir" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="toggleFolder('${jsPath}')">
|
|
3067
|
+
<span class="tree-item-icon folder-toggle">${icon}</span>
|
|
3068
|
+
<span class="tree-item-name">${escapeHtml(node.name)}</span>
|
|
3069
|
+
</div>
|
|
3070
|
+
${childrenHtml}
|
|
3071
|
+
`;
|
|
3072
|
+
} else {
|
|
3073
|
+
return `
|
|
3074
|
+
<div class="tree-item" data-type="file" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="openFileFromTree('${jsPath}')">
|
|
3075
|
+
<span class="tree-item-icon">${getFileIcon(node.name)}</span>
|
|
3076
|
+
<span class="tree-item-name">${escapeHtml(node.name)}</span>
|
|
3077
|
+
</div>
|
|
3078
|
+
`;
|
|
3079
|
+
}
|
|
3080
|
+
}).join('');
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
// Get file icon based on extension
|
|
3084
|
+
function getFileIcon(filename) {
|
|
3085
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
3086
|
+
const iconMap = {
|
|
3087
|
+
'js': '📜',
|
|
3088
|
+
'ts': '📜',
|
|
3089
|
+
'jsx': '⚛️',
|
|
3090
|
+
'tsx': '⚛️',
|
|
3091
|
+
'json': '{}',
|
|
3092
|
+
'md': '📝',
|
|
3093
|
+
'html': '🌐',
|
|
3094
|
+
'css': '🎨',
|
|
3095
|
+
'py': '🐍',
|
|
3096
|
+
'sh': '⚙️',
|
|
3097
|
+
'bash': '⚙️',
|
|
3098
|
+
'yml': '⚙️',
|
|
3099
|
+
'yaml': '⚙️',
|
|
3100
|
+
'png': '🖼️',
|
|
3101
|
+
'jpg': '🖼️',
|
|
3102
|
+
'jpeg': '🖼️',
|
|
3103
|
+
'gif': '🖼️',
|
|
3104
|
+
'svg': '🖼️',
|
|
3105
|
+
};
|
|
3106
|
+
return iconMap[ext] || '📄';
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// Toggle folder expanded/collapsed state
|
|
3110
|
+
function toggleFolder(path) {
|
|
3111
|
+
if (filesTreeExpanded.has(path)) {
|
|
3112
|
+
filesTreeExpanded.delete(path);
|
|
3113
|
+
} else {
|
|
3114
|
+
filesTreeExpanded.add(path);
|
|
3115
|
+
}
|
|
3116
|
+
renderFilesTabContent();
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// Collapse all folders
|
|
3120
|
+
function collapseAllFolders() {
|
|
3121
|
+
filesTreeExpanded.clear();
|
|
3122
|
+
renderFilesTabContent();
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
// Expand all folders
|
|
3126
|
+
function expandAllFolders() {
|
|
3127
|
+
function collectPaths(nodes) {
|
|
3128
|
+
for (const node of nodes) {
|
|
3129
|
+
if (node.type === 'dir') {
|
|
3130
|
+
filesTreeExpanded.add(node.path);
|
|
3131
|
+
if (node.children) {
|
|
3132
|
+
collectPaths(node.children);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
collectPaths(filesTreeData);
|
|
3138
|
+
renderFilesTabContent();
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// Refresh files tree
|
|
3142
|
+
async function refreshFilesTree() {
|
|
3143
|
+
await loadFilesTree();
|
|
3144
|
+
renderFilesTabContent();
|
|
3145
|
+
showToast('Files refreshed', 'success');
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
// Open file from tree click
|
|
3149
|
+
async function openFileFromTree(filePath) {
|
|
3150
|
+
try {
|
|
3151
|
+
// Check if file is already open
|
|
3152
|
+
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
3153
|
+
if (existingTab) {
|
|
3154
|
+
selectTab(existingTab.id);
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// Open the file via API
|
|
3159
|
+
const response = await fetch('/api/tabs/file', {
|
|
3160
|
+
method: 'POST',
|
|
3161
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3162
|
+
body: JSON.stringify({ path: filePath })
|
|
3163
|
+
});
|
|
3164
|
+
|
|
3165
|
+
if (!response.ok) {
|
|
3166
|
+
throw new Error(await response.text());
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// Refresh state and switch to the new tab
|
|
3170
|
+
await refresh();
|
|
3171
|
+
|
|
3172
|
+
// Find and select the new file tab
|
|
3173
|
+
const newTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
3174
|
+
if (newTab) {
|
|
3175
|
+
selectTab(newTab.id);
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
showToast(`Opened ${getFileName(filePath)}`, 'success');
|
|
3179
|
+
} catch (err) {
|
|
3180
|
+
showToast('Failed to open file: ' + err.message, 'error');
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
// ========================================
|
|
3185
|
+
// Projects Tab Functions (Spec 0045)
|
|
3186
|
+
// ========================================
|
|
3187
|
+
|
|
2813
3188
|
// Render the info header with helpful links
|
|
2814
3189
|
function renderInfoHeader() {
|
|
2815
3190
|
return `
|
|
@@ -2817,7 +3192,7 @@
|
|
|
2817
3192
|
<h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">Codev: Project View</h1>
|
|
2818
3193
|
<p>This shows the state of all projects. Our goal is to move each project through all the stages until it reaches INTGR'D (integrated). Hover over column headers to learn about each stage.</p>
|
|
2819
3194
|
<p>To add projects, update status, or approve stages, use the <strong>Architect</strong> terminal on the left.</p>
|
|
2820
|
-
<p>Docs: <a href="#" onclick="openProjectFile('codev/docs/lifecycle.md'); return false;">Lifecycle</a> · <a href="#" onclick="openProjectFile('codev/docs/commands/overview.md'); return false;">CLI Reference</a> · <a href="#" onclick="openProjectFile('codev/protocols/spider/protocol.md'); return false;">SPIDER Protocol</a> · <a href="https://github.com/cluesmith/codev#readme" target="_blank">README</a></p>
|
|
3195
|
+
<p>Docs: <a href="#" onclick="openProjectFile('codev/resources/cheatsheet.md'); return false;">Cheatsheet</a> · <a href="#" onclick="openProjectFile('codev/docs/lifecycle.md'); return false;">Lifecycle</a> · <a href="#" onclick="openProjectFile('codev/docs/commands/overview.md'); return false;">CLI Reference</a> · <a href="#" onclick="openProjectFile('codev/protocols/spider/protocol.md'); return false;">SPIDER Protocol</a> · <a href="https://github.com/cluesmith/codev#readme" target="_blank">README</a></p>
|
|
2821
3196
|
</div>
|
|
2822
3197
|
`;
|
|
2823
3198
|
}
|
package/templates/open.html
CHANGED
|
@@ -348,7 +348,7 @@
|
|
|
348
348
|
}
|
|
349
349
|
</style>
|
|
350
350
|
</head>
|
|
351
|
-
<body data-builder="{{BUILDER_ID}}" data-file="{{FILE_PATH}}" data-lang="{{LANG}}" data-is-image="{{IS_IMAGE}}" data-file-size="{{FILE_SIZE}}">
|
|
351
|
+
<body data-builder="{{BUILDER_ID}}" data-file="{{FILE_PATH}}" data-lang="{{LANG}}" data-is-image="{{IS_IMAGE}}" data-is-video="{{IS_VIDEO}}" data-file-size="{{FILE_SIZE}}">
|
|
352
352
|
<div class="header">
|
|
353
353
|
<h1 class="path">{{FILE_PATH}}</h1>
|
|
354
354
|
<div class="subtitle" id="subtitle">Click on a line number to leave an annotation.</div>
|
|
@@ -388,6 +388,16 @@
|
|
|
388
388
|
</div>
|
|
389
389
|
</div>
|
|
390
390
|
|
|
391
|
+
<!-- Video viewer mode -->
|
|
392
|
+
<div id="video-viewer" style="display: none; padding: 15px; height: calc(100vh - 80px);">
|
|
393
|
+
<div style="display: flex; align-items: center; justify-content: center; height: 100%; background: #222; border-radius: 4px;">
|
|
394
|
+
<video id="video-display" controls style="max-width: 100%; max-height: 100%;">
|
|
395
|
+
Your browser does not support the video tag.
|
|
396
|
+
</video>
|
|
397
|
+
<div id="video-error" style="display: none; color: #ef4444; text-align: center; padding: 40px;"></div>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
391
401
|
<!-- Editor mode -->
|
|
392
402
|
<textarea id="editor" spellcheck="false"></textarea>
|
|
393
403
|
|
|
@@ -448,6 +458,9 @@
|
|
|
448
458
|
let imageNaturalWidth = 0;
|
|
449
459
|
let imageNaturalHeight = 0;
|
|
450
460
|
|
|
461
|
+
// Video viewer state
|
|
462
|
+
const isVideoFile = {{IS_VIDEO}};
|
|
463
|
+
|
|
451
464
|
// Comment patterns by file extension
|
|
452
465
|
const COMMENT_PATTERNS = {
|
|
453
466
|
js: { prefix: '// REVIEW', regex: /^(\s*)\/\/\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
@@ -525,6 +538,40 @@
|
|
|
525
538
|
img.src = '/api/image?t=' + Date.now();
|
|
526
539
|
}
|
|
527
540
|
|
|
541
|
+
// Initialize video viewer
|
|
542
|
+
function initVideo(fileSize) {
|
|
543
|
+
// Hide code view elements
|
|
544
|
+
document.getElementById('viewMode').style.display = 'none';
|
|
545
|
+
document.getElementById('editBtn').style.display = 'none';
|
|
546
|
+
document.getElementById('togglePreviewBtn').style.display = 'none';
|
|
547
|
+
|
|
548
|
+
// Update subtitle
|
|
549
|
+
const sizeStr = formatFileSize(fileSize);
|
|
550
|
+
document.querySelector('.subtitle').textContent = 'Video player · ' + sizeStr;
|
|
551
|
+
|
|
552
|
+
// Show video viewer
|
|
553
|
+
const videoViewer = document.getElementById('video-viewer');
|
|
554
|
+
videoViewer.style.display = 'block';
|
|
555
|
+
|
|
556
|
+
// Load video
|
|
557
|
+
const video = document.getElementById('video-display');
|
|
558
|
+
const videoError = document.getElementById('video-error');
|
|
559
|
+
|
|
560
|
+
video.onloadedmetadata = function() {
|
|
561
|
+
videoError.style.display = 'none';
|
|
562
|
+
video.style.display = 'block';
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
video.onerror = function() {
|
|
566
|
+
videoError.textContent = 'Failed to load video. The file may be corrupted or in an unsupported format.';
|
|
567
|
+
videoError.style.display = 'block';
|
|
568
|
+
video.style.display = 'none';
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
// Add cache-busting query param to allow reload
|
|
572
|
+
video.src = '/api/video?t=' + Date.now();
|
|
573
|
+
}
|
|
574
|
+
|
|
528
575
|
// Update image info display (in both header and controls)
|
|
529
576
|
function updateImageInfo(fileSize) {
|
|
530
577
|
const sizeStr = formatFileSize(fileSize);
|