codex-lens 0.1.27 → 0.1.29
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/dist/aggregator.js +40 -2
- package/dist/public/assets/{main-d-2BqwRB.js → main-8FK9vFAz.js} +40 -38
- package/dist/public/assets/{main-DNXrKVO-.css → main-CnRUPtz5.css} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/watcher.js +16 -0
- package/package.json +1 -1
- package/src/aggregator.js +43 -2
- package/src/components/App.jsx +169 -1
- package/src/components/CodeViewer.jsx +2 -2
- package/src/components/ErrorBoundary.jsx +172 -0
- package/src/components/TerminalPanel.jsx +154 -163
- package/src/git-manager.js +168 -0
- package/src/global.css +304 -0
- package/src/main.jsx +10 -2
- package/src/watcher.js +20 -0
- package/tsconfig.json +24 -0
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
* The original design remains. The terminal itself
|
|
30
30
|
* has been extended to include xterm CSI codes, among
|
|
31
31
|
* other features.
|
|
32
|
-
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg-primary: #000000;--bg-secondary: #0A0A0A;--bg-tertiary: #141414;--bg-elevated: #1A1A1A;--bg-surface: #0F0F0F;--text-primary: #F8FAFC;--text-secondary: #94A3B8;--text-muted: #64748B;--border-color: rgba(148, 163, 184, .15);--border-glow: rgba(34, 197, 94, .3);--accent-color: #22C55E;--accent-hover: #16A34A;--accent-glow: rgba(34, 197, 94, .4);--accent-soft: rgba(34, 197, 94, .1);--diff-add-bg: rgba(34, 197, 94, .12);--diff-add-text: #4ADE80;--diff-add-border: #22C55E;--diff-remove-bg: rgba(239, 68, 68, .12);--diff-remove-text: #F87171;--diff-remove-border: #EF4444;--danger-color: #EF4444;--danger-hover: #DC2626;--success-color: #22C55E;--success-hover: #16A34A;--warning-color: #F59E0B;--info-color: #3B82F6;--font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-display: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Consolas", monospace;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .2);--shadow-sm: 0 2px 4px rgba(0, 0, 0, .2);--shadow-md: 0 4px 12px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .4);--shadow-xl: 0 16px 48px rgba(0, 0, 0, .5);--shadow-glow: 0 0 24px var(--accent-glow);--shadow-glow-sm: 0 0 12px var(--accent-glow);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 14px;--radius-xl: 20px;--transition-fast: .12s ease;--transition-normal: .2s ease;--transition-slow: .3s ease;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden}body{font-family:var(--font-family);background:var(--bg-primary);color:var(--text-primary);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;background:var(--bg-primary)}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:linear-gradient(180deg,#1e293bf2,#1e293bd9);-webkit-backdrop-filter:blur(var(--blur-md));backdrop-filter:blur(var(--blur-md));border-bottom:1px solid var(--border-color);min-height:52px;position:relative;z-index:100}.top-bar:after{content:"";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent-color),transparent);opacity:.3}.top-bar-left,.top-bar-center,.top-bar-right{display:flex;align-items:center;gap:16px}.top-bar-left{width:260px}.top-bar-center{flex:1;justify-content:center}.top-bar-right{width:40%;justify-content:flex-end}.top-bar-title{font-family:var(--font-display);font-weight:600;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:flex;align-items:center;gap:8px}.top-bar-title:before{content:"";display:inline-block;width:3px;height:14px;background:var(--accent-color);border-radius:2px}.main-content{display:flex;flex:1;overflow:hidden;gap:1px;background:var(--border-color)}.panel{display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--bg-secondary) 0%,rgba(30,41,59,.95) 100%)}.left-panel{width:260px;flex-shrink:0}.middle-panel{flex:1;min-width:300px}.right-panel{width:40%;min-width:300px;max-width:60%}.panel-content{flex:1;overflow:auto;padding:8px}.file-tree{padding:4px}.file-item{padding:8px 12px;cursor:pointer;border-radius:var(--radius-sm);display:flex;align-items:center;gap:10px;transition:all var(--transition-fast);position:relative;margin:2px 0}.file-item:before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:0;background:var(--accent-color);border-radius:0 2px 2px 0;transition:height var(--transition-fast)}.file-item:hover{background:#94a3b814}.file-item:hover:before{height:60%}.file-item:active{transform:scale(.98)}.file-item.active{background:var(--accent-soft);color:var(--text-primary)}.file-item.active:before{height:70%}.file-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:14px;opacity:.9}.file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.diff-line{font-family:var(--font-mono);font-size:13px;padding:3px 16px 3px 20px;white-space:pre;border-left:3px solid transparent;transition:background var(--transition-fast);position:relative}.diff-line.added{background:var(--diff-add-bg);color:var(--diff-add-text);border-left-color:var(--diff-add-border)}.diff-line.removed{background:var(--diff-remove-bg);color:var(--diff-remove-text);border-left-color:var(--diff-remove-border)}.diff-line:before{display:inline-block;width:16px;margin-left:-16px;text-align:center;font-weight:600;font-size:12px}.diff-line.added:before{content:"+";color:var(--diff-add-text)}.diff-line.removed:before{content:"-";color:var(--diff-remove-text)}.tab-bar{display:flex;background:#0f172a99;border-bottom:1px solid var(--border-color);overflow-x:auto;min-height:40px;scrollbar-width:none}.tab-bar::-webkit-scrollbar{display:none}.tab{display:flex;align-items:center;gap:8px;padding:10px 16px;background:transparent;border-right:1px solid var(--border-color);cursor:pointer;min-width:100px;max-width:180px;font-size:13px;color:var(--text-muted);transition:all var(--transition-fast);position:relative}.tab:hover{background:#94a3b80d;color:var(--text-secondary)}.tab.active{background:var(--bg-secondary);color:var(--text-primary)}.tab.active:after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--accent-color)}.tab-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:2px 6px;border-radius:var(--radius-xs);line-height:1;transition:all var(--transition-fast);opacity:.6}.tab-close:hover{background:#ef444433;color:var(--danger-color);opacity:1}.tab.modified .tab-name{color:var(--accent-color)}.tab-modified-mark{color:var(--accent-color);margin-right:4px;font-size:10px}.context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:160px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}@keyframes contextMenuIn{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}.context-menu-item{padding:10px 14px;cursor:pointer;font-size:13px;color:var(--text-primary);border-radius:var(--radius-sm);transition:all var(--transition-fast)}.context-menu-item:hover{background:var(--accent-soft);color:var(--accent-color)}.file-context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:200px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.ws-status{display:inline-block;width:8px;height:8px;border-radius:50%;margin-left:8px;transition:all var(--transition-normal)}.ws-status.connected{background:var(--success-color);box-shadow:0 0 8px var(--success-color);animation:pulse 2s infinite}.ws-status.disconnected{background:var(--danger-color);box-shadow:0 0 8px var(--danger-color)}@keyframes pulse{0%,to{opacity:1;box-shadow:0 0 8px var(--success-color)}50%{opacity:.7;box-shadow:0 0 4px var(--success-color)}}.version-info{display:flex;align-items:center;gap:10px}.version-number{font-size:11px;color:var(--text-muted);font-weight:500;font-family:var(--font-mono);padding:3px 8px;background:#94a3b81a;border-radius:var(--radius-xs)}.update-badge{font-size:10px;padding:4px 10px;background:linear-gradient(135deg,var(--warning-color) 0%,#FBBF24 100%);color:var(--bg-primary);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;transition:all var(--transition-fast)}.update-badge:hover{transform:translateY(-1px);box-shadow:0 4px 12px #f59e0b4d}.terminal-wrapper{flex:1;overflow:hidden;min-height:0}.section{margin-bottom:8px}.section-title{padding:10px 12px 6px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.8px}.empty-state{padding:40px 24px;text-align:center;color:var(--text-muted);font-size:13px;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state:before{content:"";width:48px;height:48px;background:linear-gradient(135deg,var(--bg-tertiary) 0%,var(--bg-elevated) 100%);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.code-panel{font-family:var(--font-mono)}.code-viewer-codemirror{height:100%;width:100%;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-theme{flex:1;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-editor{flex:1;min-width:0;min-height:0}.code-viewer-codemirror .cm-scroller{flex:1;overflow:auto!important;min-height:0}.code-content{padding:16px;white-space:pre-wrap;word-break:break-all;line-height:1.7;font-size:13px}.diff-container{font-family:var(--font-mono);font-size:13px;padding:8px 0}.task-btn{padding:8px 18px;border:none;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast);font-family:var(--font-family);display:inline-flex;align-items:center;gap:6px}.task-btn:hover{transform:translateY(-1px)}.task-btn:active{transform:translateY(0)}.task-btn-clear{background:transparent;color:var(--text-secondary);border:1px solid var(--border-color)}.task-btn-clear:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color);box-shadow:var(--shadow-glow-sm)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-elevated);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}::-webkit-scrollbar-corner{background:transparent}@media (max-width: 1024px){.left-panel{width:220px}.right-panel{width:45%;min-width:250px}.top-bar-left{width:220px}}@media (max-width: 768px){.main-content{flex-direction:column}.left-panel,.right-panel{width:100%;max-width:none;min-width:auto}.middle-panel{min-height:300px}.top-bar{flex-wrap:wrap;padding:10px 16px}.top-bar-left,.top-bar-center,.top-bar-right{width:auto}.top-bar-center{order:3;width:100%;justify-content:flex-start;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}}
|
|
32
|
+
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg-primary: #000000;--bg-secondary: #0A0A0A;--bg-tertiary: #141414;--bg-elevated: #1A1A1A;--bg-surface: #0F0F0F;--text-primary: #F8FAFC;--text-secondary: #94A3B8;--text-muted: #64748B;--border-color: rgba(148, 163, 184, .15);--border-glow: rgba(34, 197, 94, .3);--accent-color: #22C55E;--accent-hover: #16A34A;--accent-glow: rgba(34, 197, 94, .4);--accent-soft: rgba(34, 197, 94, .1);--diff-add-bg: rgba(34, 197, 94, .12);--diff-add-text: #4ADE80;--diff-add-border: #22C55E;--diff-remove-bg: rgba(239, 68, 68, .12);--diff-remove-text: #F87171;--diff-remove-border: #EF4444;--danger-color: #EF4444;--danger-hover: #DC2626;--success-color: #22C55E;--success-hover: #16A34A;--warning-color: #F59E0B;--info-color: #3B82F6;--font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-display: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Consolas", monospace;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .2);--shadow-sm: 0 2px 4px rgba(0, 0, 0, .2);--shadow-md: 0 4px 12px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .4);--shadow-xl: 0 16px 48px rgba(0, 0, 0, .5);--shadow-glow: 0 0 24px var(--accent-glow);--shadow-glow-sm: 0 0 12px var(--accent-glow);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 14px;--radius-xl: 20px;--transition-fast: .12s ease;--transition-normal: .2s ease;--transition-slow: .3s ease;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden}body{font-family:var(--font-family);background:var(--bg-primary);color:var(--text-primary);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;background:var(--bg-primary)}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:linear-gradient(180deg,#1e293bf2,#1e293bd9);-webkit-backdrop-filter:blur(var(--blur-md));backdrop-filter:blur(var(--blur-md));border-bottom:1px solid var(--border-color);min-height:52px;position:relative;z-index:100}.top-bar:after{content:"";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent-color),transparent);opacity:.3}.top-bar-left,.top-bar-center,.top-bar-right{display:flex;align-items:center;gap:16px}.top-bar-left{width:260px}.top-bar-center{flex:1;justify-content:center}.top-bar-right{width:40%;justify-content:flex-end}.top-bar-title{font-family:var(--font-display);font-weight:600;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:flex;align-items:center;gap:8px}.top-bar-title:before{content:"";display:inline-block;width:3px;height:14px;background:var(--accent-color);border-radius:2px}.main-content{display:flex;flex:1;overflow:hidden;gap:1px;background:var(--border-color)}.panel{display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--bg-secondary) 0%,rgba(30,41,59,.95) 100%)}.left-panel{width:260px;flex-shrink:0}.middle-panel{flex:1;min-width:300px}.right-panel{width:40%;min-width:300px;max-width:60%}.panel-content{flex:1;overflow:auto;padding:8px}.file-tree{padding:4px}.file-item{padding:8px 12px;cursor:pointer;border-radius:var(--radius-sm);display:flex;align-items:center;gap:10px;transition:all var(--transition-fast);position:relative;margin:2px 0}.file-item:before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:0;background:var(--accent-color);border-radius:0 2px 2px 0;transition:height var(--transition-fast)}.file-item:hover{background:#94a3b814}.file-item:hover:before{height:60%}.file-item:active{transform:scale(.98)}.file-item.active{background:var(--accent-soft);color:var(--text-primary)}.file-item.active:before{height:70%}.file-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:14px;opacity:.9}.file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.diff-line{font-family:var(--font-mono);font-size:13px;padding:3px 16px 3px 20px;white-space:pre;border-left:3px solid transparent;transition:background var(--transition-fast);position:relative}.diff-line.added{background:var(--diff-add-bg);color:var(--diff-add-text);border-left-color:var(--diff-add-border)}.diff-line.removed{background:var(--diff-remove-bg);color:var(--diff-remove-text);border-left-color:var(--diff-remove-border)}.diff-line:before{display:inline-block;width:16px;margin-left:-16px;text-align:center;font-weight:600;font-size:12px}.diff-line.added:before{content:"+";color:var(--diff-add-text)}.diff-line.removed:before{content:"-";color:var(--diff-remove-text)}.tab-bar{display:flex;background:#0f172a99;border-bottom:1px solid var(--border-color);overflow-x:auto;min-height:40px;scrollbar-width:none}.tab-bar::-webkit-scrollbar{display:none}.tab{display:flex;align-items:center;gap:8px;padding:10px 16px;background:transparent;border-right:1px solid var(--border-color);cursor:pointer;min-width:100px;max-width:180px;font-size:13px;color:var(--text-muted);transition:all var(--transition-fast);position:relative}.tab:hover{background:#94a3b80d;color:var(--text-secondary)}.tab.active{background:var(--bg-secondary);color:var(--text-primary)}.tab.active:after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--accent-color)}.tab-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:2px 6px;border-radius:var(--radius-xs);line-height:1;transition:all var(--transition-fast);opacity:.6}.tab-close:hover{background:#ef444433;color:var(--danger-color);opacity:1}.tab.modified .tab-name{color:var(--accent-color)}.tab-modified-mark{color:var(--accent-color);margin-right:4px;font-size:10px}.context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:160px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}@keyframes contextMenuIn{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}.context-menu-item{padding:10px 14px;cursor:pointer;font-size:13px;color:var(--text-primary);border-radius:var(--radius-sm);transition:all var(--transition-fast)}.context-menu-item:hover{background:var(--accent-soft);color:var(--accent-color)}.file-context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:200px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.ws-status{display:inline-block;width:8px;height:8px;border-radius:50%;margin-left:8px;transition:all var(--transition-normal)}.ws-status.connected{background:var(--success-color);box-shadow:0 0 8px var(--success-color);animation:pulse 2s infinite}.ws-status.disconnected{background:var(--danger-color);box-shadow:0 0 8px var(--danger-color)}@keyframes pulse{0%,to{opacity:1;box-shadow:0 0 8px var(--success-color)}50%{opacity:.7;box-shadow:0 0 4px var(--success-color)}}.version-info{display:flex;align-items:center;gap:10px}.version-number{font-size:11px;color:var(--text-muted);font-weight:500;font-family:var(--font-mono);padding:3px 8px;background:#94a3b81a;border-radius:var(--radius-xs)}.update-badge{font-size:10px;padding:4px 10px;background:linear-gradient(135deg,var(--warning-color) 0%,#FBBF24 100%);color:var(--bg-primary);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;transition:all var(--transition-fast)}.update-badge:hover{transform:translateY(-1px);box-shadow:0 4px 12px #f59e0b4d}.terminal-wrapper{flex:1;overflow:hidden;min-height:0}.section{margin-bottom:8px}.section-title{padding:10px 12px 6px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.8px}.empty-state{padding:40px 24px;text-align:center;color:var(--text-muted);font-size:13px;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state:before{content:"";width:48px;height:48px;background:linear-gradient(135deg,var(--bg-tertiary) 0%,var(--bg-elevated) 100%);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.code-panel{font-family:var(--font-mono)}.code-viewer-codemirror{height:100%;width:100%;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-theme{flex:1;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-editor{flex:1;min-width:0;min-height:0}.code-viewer-codemirror .cm-scroller{flex:1;overflow:auto!important;min-height:0}.code-content{padding:16px;white-space:pre-wrap;word-break:break-all;line-height:1.7;font-size:13px}.diff-container{font-family:var(--font-mono);font-size:13px;padding:8px 0}.task-btn{padding:8px 18px;border:none;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast);font-family:var(--font-family);display:inline-flex;align-items:center;gap:6px}.task-btn:hover{transform:translateY(-1px)}.task-btn:active{transform:translateY(0)}.task-btn-clear{background:transparent;color:var(--text-secondary);border:1px solid var(--border-color)}.task-btn-clear:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color);box-shadow:var(--shadow-glow-sm)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-elevated);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}::-webkit-scrollbar-corner{background:transparent}.git-status-bar{display:flex;align-items:center;gap:16px;padding:6px 14px;background:#94a3b814;border-radius:var(--radius-sm);border:1px solid var(--border-color)}.git-branch{display:flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:12px;font-weight:600;color:var(--accent-color)}.git-branch-icon{font-size:14px;opacity:.8}.git-stats{display:flex;align-items:center;gap:12px}.git-staged,.git-unstaged{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text-secondary)}.git-staged-count{font-weight:600;color:var(--success-color)}.git-unstaged-count{font-weight:600;color:var(--warning-color)}.git-changes-btn{padding:4px 10px;font-size:11px;background:transparent;border:1px solid var(--border-color);border-radius:var(--radius-xs);color:var(--text-secondary);cursor:pointer;transition:all var(--transition-fast)}.git-changes-btn:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color)}.staged-changes-panel{position:absolute;top:52px;left:0;right:0;max-height:400px;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border-bottom:1px solid var(--border-color);z-index:99;overflow-y:auto;animation:slideDown .2s ease}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.staged-panel-header{display:flex;justify-content:space-between;align-items:center;padding:10px 16px;background:#0f172a99;border-bottom:1px solid var(--border-color);font-weight:600;font-size:12px;color:var(--text-secondary)}.staged-panel-actions{display:flex;gap:8px;align-items:center}.stage-btn,.unstage-btn{padding:4px 10px;font-size:11px;background:var(--bg-elevated);border:1px solid var(--border-color);border-radius:var(--radius-xs);color:var(--text-secondary);cursor:pointer;transition:all var(--transition-fast)}.stage-btn:hover,.unstage-btn:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color)}.close-btn{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--text-muted);cursor:pointer;font-size:16px;border-radius:var(--radius-xs);transition:all var(--transition-fast)}.close-btn:hover{background:#ef444433;color:var(--danger-color)}.changes-section{padding:8px 0;border-bottom:1px solid var(--border-color)}.changes-section:last-child{border-bottom:none}.changes-section-header{padding:4px 16px}.changes-section-title{font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.changes-list{display:flex;flex-direction:column}.change-item{display:flex;align-items:center;gap:8px;padding:6px 16px;cursor:pointer;transition:background var(--transition-fast)}.change-item:hover{background:#94a3b814}.change-item.staged{background:var(--accent-soft)}.change-icon{width:16px;text-align:center;font-size:12px;color:var(--text-muted)}.change-item.staged .change-icon{color:var(--success-color)}.change-status{width:14px;height:14px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;font-family:var(--font-mono);border-radius:2px}.change-status.M{background:var(--warning-color);color:var(--bg-primary)}.change-status.A{background:var(--success-color);color:var(--bg-primary)}.change-status.D{background:var(--danger-color);color:var(--text-primary)}.change-status.R{background:var(--info-color);color:var(--text-primary)}.change-status.\?{background:var(--text-muted);color:var(--bg-primary)}.change-path{flex:1;font-size:12px;font-family:var(--font-mono);color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.no-changes{padding:24px;text-align:center;color:var(--text-muted);font-size:13px}.commit-section{display:flex;gap:8px;padding:12px 16px;background:#0f172a99;border-top:1px solid var(--border-color)}.commit-input{flex:1;padding:8px 12px;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:var(--radius-sm);color:var(--text-primary);font-size:12px;font-family:var(--font-family)}.commit-input:focus{outline:none;border-color:var(--accent-color)}.commit-btn{padding:8px 16px;background:var(--accent-color);border:none;border-radius:var(--radius-sm);color:var(--bg-primary);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast)}.commit-btn:hover{background:var(--accent-hover);transform:translateY(-1px)}.file-git-badge{width:14px;height:14px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;font-family:var(--font-mono);border-radius:2px;margin-right:2px}.file-git-badge.staged{background:var(--success-color);color:var(--bg-primary)}.file-git-badge.wt{background:var(--warning-color);color:var(--bg-primary)}@media (max-width: 1024px){.left-panel{width:220px}.right-panel{width:45%;min-width:250px}.top-bar-left{width:220px}}@media (max-width: 768px){.main-content{flex-direction:column}.left-panel,.right-panel{width:100%;max-width:none;min-width:auto}.middle-panel{min-height:300px}.top-bar{flex-wrap:wrap;padding:10px 16px}.top-bar-left,.top-bar-center,.top-bar-right{width:auto}.top-bar-center{order:3;width:100%;justify-content:flex-start;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}}
|
package/dist/public/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Lens</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/main-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/main-
|
|
7
|
+
<script type="module" crossorigin src="./assets/main-8FK9vFAz.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/main-CnRUPtz5.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/dist/watcher.js
CHANGED
|
@@ -109,6 +109,21 @@ class FileWatcher {
|
|
|
109
109
|
this.wsEmitter = wsEmitter;
|
|
110
110
|
this.watcher = null;
|
|
111
111
|
this.fileContents = /* @__PURE__ */ new Map();
|
|
112
|
+
this.gitStatusCallback = null;
|
|
113
|
+
this._gitStatusTimeout = null;
|
|
114
|
+
}
|
|
115
|
+
setGitStatusCallback(callback) {
|
|
116
|
+
this.gitStatusCallback = callback;
|
|
117
|
+
}
|
|
118
|
+
_scheduleGitStatusUpdate(filePath) {
|
|
119
|
+
if (this.gitStatusCallback) {
|
|
120
|
+
if (this._gitStatusTimeout) {
|
|
121
|
+
clearTimeout(this._gitStatusTimeout);
|
|
122
|
+
}
|
|
123
|
+
this._gitStatusTimeout = setTimeout(() => {
|
|
124
|
+
this.gitStatusCallback(filePath);
|
|
125
|
+
}, 500);
|
|
126
|
+
}
|
|
112
127
|
}
|
|
113
128
|
async start() {
|
|
114
129
|
const ignored = [
|
|
@@ -174,6 +189,7 @@ class FileWatcher {
|
|
|
174
189
|
}
|
|
175
190
|
this.emitFileChange(filePath, newContent, diff);
|
|
176
191
|
}
|
|
192
|
+
this._scheduleGitStatusUpdate(filePath);
|
|
177
193
|
} catch (error) {
|
|
178
194
|
logger.errorWithStack(`Error handling file change: ${filePath}`, error);
|
|
179
195
|
}
|
package/package.json
CHANGED
package/src/aggregator.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
|
-
import { createHash, createHash as cryptoCreateHash } from 'crypto';
|
|
3
2
|
import { WebSocketServer } from 'ws';
|
|
4
3
|
import express from 'express';
|
|
5
4
|
import { spawn } from 'child_process';
|
|
@@ -7,6 +6,7 @@ import { createProxyServer } from './proxy.js';
|
|
|
7
6
|
import { FileWatcher, scanDirectory } from './watcher.js';
|
|
8
7
|
import { createLogger } from './lib/logger.js';
|
|
9
8
|
import { spawnCodex, writeToPty, resizePty, killPty, onPtyData, onPtyExit, getPtyState, getOutputBuffer } from './pty-manager.js';
|
|
9
|
+
import { createGitManager } from './git-manager.js';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
@@ -56,6 +56,7 @@ class Aggregator {
|
|
|
56
56
|
this.proxyServer = null;
|
|
57
57
|
this.fileWatcher = null;
|
|
58
58
|
this.ptyProcess = null;
|
|
59
|
+
this.gitManager = null;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
async start(proxyPort) {
|
|
@@ -158,6 +159,19 @@ class Aggregator {
|
|
|
158
159
|
this.fileWatcher = new FileWatcher(this.projectRoot, (event) => this.broadcast(event));
|
|
159
160
|
await this.fileWatcher.start();
|
|
160
161
|
|
|
162
|
+
// Initialize Git Manager
|
|
163
|
+
this.gitManager = createGitManager(this.projectRoot, (event) => this.broadcast(event));
|
|
164
|
+
if (this.gitManager.isGitRepo()) {
|
|
165
|
+
await this.gitManager.broadcastUpdate();
|
|
166
|
+
logger.info('Git repository detected, git status broadcasting enabled');
|
|
167
|
+
// Set up git status update trigger on file changes
|
|
168
|
+
this.fileWatcher.setGitStatusCallback((filePath) => {
|
|
169
|
+
if (this.gitManager?.isGitRepo()) {
|
|
170
|
+
this.gitManager.scheduleStatusUpdate();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
await this.startCodex(proxyPort);
|
|
162
176
|
|
|
163
177
|
return { httpPort: HTTP_PORT, proxyPort };
|
|
@@ -249,7 +263,7 @@ class Aggregator {
|
|
|
249
263
|
}
|
|
250
264
|
}
|
|
251
265
|
|
|
252
|
-
handleClientMessage(data, ws) {
|
|
266
|
+
async handleClientMessage(data, ws) {
|
|
253
267
|
if (data.type === 'user_message') {
|
|
254
268
|
writeToPty(data.data + '\r');
|
|
255
269
|
} else if (data.type === 'open_file') {
|
|
@@ -267,6 +281,33 @@ class Aggregator {
|
|
|
267
281
|
}
|
|
268
282
|
}));
|
|
269
283
|
}
|
|
284
|
+
} else if (data.type === 'git_status_request') {
|
|
285
|
+
if (this.gitManager?.isGitRepo()) {
|
|
286
|
+
this.gitManager.broadcastUpdate();
|
|
287
|
+
}
|
|
288
|
+
} else if (data.type === 'git_stage') {
|
|
289
|
+
if (this.gitManager?.isGitRepo()) {
|
|
290
|
+
if (data.filePath) {
|
|
291
|
+
await this.gitManager.stageFile(data.filePath);
|
|
292
|
+
} else {
|
|
293
|
+
await this.gitManager.stageAll();
|
|
294
|
+
}
|
|
295
|
+
this.gitManager.broadcastUpdate();
|
|
296
|
+
}
|
|
297
|
+
} else if (data.type === 'git_unstage') {
|
|
298
|
+
if (this.gitManager?.isGitRepo()) {
|
|
299
|
+
if (data.filePath) {
|
|
300
|
+
await this.gitManager.unstageFile(data.filePath);
|
|
301
|
+
} else {
|
|
302
|
+
await this.gitManager.unstageAll();
|
|
303
|
+
}
|
|
304
|
+
this.gitManager.broadcastUpdate();
|
|
305
|
+
}
|
|
306
|
+
} else if (data.type === 'git_commit') {
|
|
307
|
+
if (this.gitManager?.isGitRepo()) {
|
|
308
|
+
await this.gitManager.commit(data.message);
|
|
309
|
+
this.gitManager.broadcastUpdate();
|
|
310
|
+
}
|
|
270
311
|
}
|
|
271
312
|
}
|
|
272
313
|
|
package/src/components/App.jsx
CHANGED
|
@@ -13,6 +13,14 @@ export function App() {
|
|
|
13
13
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
14
14
|
const [projectName, setProjectName] = useState('');
|
|
15
15
|
const [saving, setSaving] = useState(false);
|
|
16
|
+
const [gitInfo, setGitInfo] = useState({
|
|
17
|
+
isRepo: false,
|
|
18
|
+
branch: null,
|
|
19
|
+
status: null,
|
|
20
|
+
stagedCount: 0,
|
|
21
|
+
unstagedCount: 0
|
|
22
|
+
});
|
|
23
|
+
const [showStagedPanel, setShowStagedPanel] = useState(false);
|
|
16
24
|
const wsRef = useRef(null);
|
|
17
25
|
|
|
18
26
|
useEffect(() => {
|
|
@@ -122,6 +130,15 @@ export function App() {
|
|
|
122
130
|
case 'file_content':
|
|
123
131
|
openFileInTab(msg.data);
|
|
124
132
|
break;
|
|
133
|
+
case 'git_status':
|
|
134
|
+
setGitInfo({
|
|
135
|
+
isRepo: msg.data.isRepo,
|
|
136
|
+
branch: msg.data.branch,
|
|
137
|
+
status: msg.data.status,
|
|
138
|
+
stagedCount: msg.data.stagedCount,
|
|
139
|
+
unstagedCount: msg.data.unstagedCount
|
|
140
|
+
});
|
|
141
|
+
break;
|
|
125
142
|
case 'connected':
|
|
126
143
|
console.log('Server confirmed connection');
|
|
127
144
|
break;
|
|
@@ -130,6 +147,24 @@ export function App() {
|
|
|
130
147
|
}
|
|
131
148
|
}
|
|
132
149
|
|
|
150
|
+
function sendGitCommand(type, payload) {
|
|
151
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
152
|
+
wsRef.current.send(JSON.stringify({ type, ...payload }));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function handleStage(path) {
|
|
157
|
+
sendGitCommand('git_stage', { filePath: path });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function handleUnstage(path) {
|
|
161
|
+
sendGitCommand('git_unstage', { filePath: path });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handleCommit(message) {
|
|
165
|
+
sendGitCommand('git_commit', { message });
|
|
166
|
+
}
|
|
167
|
+
|
|
133
168
|
function openFileInTab(data) {
|
|
134
169
|
const fileName = data.path.split(/[/\\]/).pop();
|
|
135
170
|
|
|
@@ -380,6 +415,30 @@ export function App() {
|
|
|
380
415
|
<span className="top-bar-title">当前工作区: {projectName}</span>
|
|
381
416
|
</div>
|
|
382
417
|
<div className="top-bar-center">
|
|
418
|
+
{gitInfo.isRepo && (
|
|
419
|
+
<div className="git-status-bar">
|
|
420
|
+
<span className="git-branch">
|
|
421
|
+
<span className="git-branch-icon">⎇</span>
|
|
422
|
+
{gitInfo.branch || 'main'}
|
|
423
|
+
</span>
|
|
424
|
+
<div className="git-stats">
|
|
425
|
+
<span className="git-staged" title="已暂存">
|
|
426
|
+
<span className="git-staged-count">{gitInfo.stagedCount}</span>
|
|
427
|
+
<span className="git-staged-label">已暂存</span>
|
|
428
|
+
</span>
|
|
429
|
+
<span className="git-unstaged" title="未暂存">
|
|
430
|
+
<span className="git-unstaged-count">{gitInfo.unstagedCount}</span>
|
|
431
|
+
<span className="git-unstaged-label">未暂存</span>
|
|
432
|
+
</span>
|
|
433
|
+
</div>
|
|
434
|
+
<button
|
|
435
|
+
className="git-changes-btn"
|
|
436
|
+
onClick={() => setShowStagedPanel(!showStagedPanel)}
|
|
437
|
+
>
|
|
438
|
+
{showStagedPanel ? '隐藏变更' : '查看变更'}
|
|
439
|
+
</button>
|
|
440
|
+
</div>
|
|
441
|
+
)}
|
|
383
442
|
<button className="task-btn task-btn-clear" onClick={clearAllDiff} title="清空所有 diff 显示">
|
|
384
443
|
清空 diff
|
|
385
444
|
</button>
|
|
@@ -402,6 +461,7 @@ export function App() {
|
|
|
402
461
|
files={files}
|
|
403
462
|
activeFile={activeTab?.path || null}
|
|
404
463
|
onFileClick={handleFileClick}
|
|
464
|
+
gitInfo={gitInfo}
|
|
405
465
|
/>
|
|
406
466
|
<div className="panel middle-panel">
|
|
407
467
|
<TabBar
|
|
@@ -455,6 +515,15 @@ export function App() {
|
|
|
455
515
|
}}
|
|
456
516
|
/>
|
|
457
517
|
)}
|
|
518
|
+
{showStagedPanel && gitInfo.isRepo && (
|
|
519
|
+
<StagedChangesPanel
|
|
520
|
+
gitInfo={gitInfo}
|
|
521
|
+
onStage={handleStage}
|
|
522
|
+
onUnstage={handleUnstage}
|
|
523
|
+
onCommit={handleCommit}
|
|
524
|
+
onClose={() => setShowStagedPanel(false)}
|
|
525
|
+
/>
|
|
526
|
+
)}
|
|
458
527
|
<div className="panel right-panel">
|
|
459
528
|
<div className="terminal-wrapper">
|
|
460
529
|
<TerminalPanel />
|
|
@@ -516,7 +585,93 @@ function ContextMenu({ x, y, tab, tabs, saving, onClose, onCloseTab, onCloseOthe
|
|
|
516
585
|
);
|
|
517
586
|
}
|
|
518
587
|
|
|
519
|
-
function
|
|
588
|
+
function StagedChangesPanel({ gitInfo, onStage, onUnstage, onCommit, onClose }) {
|
|
589
|
+
if (!gitInfo.isRepo || !gitInfo.status) return null;
|
|
590
|
+
|
|
591
|
+
const { staged, unstaged, untracked } = gitInfo.status;
|
|
592
|
+
|
|
593
|
+
return (
|
|
594
|
+
<div className="staged-changes-panel">
|
|
595
|
+
<div className="staged-panel-header">
|
|
596
|
+
<span>Git 变更</span>
|
|
597
|
+
<div className="staged-panel-actions">
|
|
598
|
+
<button onClick={() => onStage(null)} className="stage-btn">Stage All</button>
|
|
599
|
+
<button onClick={() => onUnstage(null)} className="unstage-btn">Unstage All</button>
|
|
600
|
+
<button onClick={onClose} className="close-btn">×</button>
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
|
|
604
|
+
{staged?.length > 0 && (
|
|
605
|
+
<div className="changes-section">
|
|
606
|
+
<div className="changes-section-header">
|
|
607
|
+
<span className="changes-section-title">已暂存 ({staged.length})</span>
|
|
608
|
+
</div>
|
|
609
|
+
<div className="changes-list">
|
|
610
|
+
{staged.map((file, i) => (
|
|
611
|
+
<div key={i} className="change-item staged" onClick={() => onUnstage(file.path)}>
|
|
612
|
+
<span className="change-icon">✓</span>
|
|
613
|
+
<span className={`change-status ${file.indexStatus}`}>{file.indexStatus}</span>
|
|
614
|
+
<span className="change-path">{file.path}</span>
|
|
615
|
+
</div>
|
|
616
|
+
))}
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
)}
|
|
620
|
+
|
|
621
|
+
{unstaged?.length > 0 && (
|
|
622
|
+
<div className="changes-section">
|
|
623
|
+
<div className="changes-section-header">
|
|
624
|
+
<span className="changes-section-title">未暂存修改 ({unstaged.length})</span>
|
|
625
|
+
</div>
|
|
626
|
+
<div className="changes-list">
|
|
627
|
+
{unstaged.map((file, i) => (
|
|
628
|
+
<div key={i} className="change-item" onClick={() => onStage(file.path)}>
|
|
629
|
+
<span className="change-icon">○</span>
|
|
630
|
+
<span className={`change-status ${file.workTreeStatus}`}>{file.workTreeStatus}</span>
|
|
631
|
+
<span className="change-path">{file.path}</span>
|
|
632
|
+
</div>
|
|
633
|
+
))}
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
)}
|
|
637
|
+
|
|
638
|
+
{untracked?.length > 0 && (
|
|
639
|
+
<div className="changes-section">
|
|
640
|
+
<div className="changes-section-header">
|
|
641
|
+
<span className="changes-section-title">未跟踪 ({untracked.length})</span>
|
|
642
|
+
</div>
|
|
643
|
+
<div className="changes-list">
|
|
644
|
+
{untracked.map((file, i) => (
|
|
645
|
+
<div key={i} className="change-item untracked" onClick={() => onStage(file.path)}>
|
|
646
|
+
<span className="change-icon">?</span>
|
|
647
|
+
<span className="change-status">?</span>
|
|
648
|
+
<span className="change-path">{file.path}</span>
|
|
649
|
+
</div>
|
|
650
|
+
))}
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
)}
|
|
654
|
+
|
|
655
|
+
{((!staged?.length) && (!unstaged?.length) && (!untracked?.length)) && (
|
|
656
|
+
<div className="no-changes">无变更</div>
|
|
657
|
+
)}
|
|
658
|
+
|
|
659
|
+
{staged?.length > 0 && (
|
|
660
|
+
<div className="commit-section">
|
|
661
|
+
<input type="text" className="commit-input" placeholder="提交信息..." id="commit-message" />
|
|
662
|
+
<button className="commit-btn" onClick={() => {
|
|
663
|
+
const msg = document.getElementById('commit-message')?.value;
|
|
664
|
+
if (msg) onCommit(msg);
|
|
665
|
+
}}>
|
|
666
|
+
Commit
|
|
667
|
+
</button>
|
|
668
|
+
</div>
|
|
669
|
+
)}
|
|
670
|
+
</div>
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function LeftPanel({ files, activeFile, onFileClick, gitInfo }) {
|
|
520
675
|
const [expandedDirs, setExpandedDirs] = useState({});
|
|
521
676
|
const [contextMenu, setContextMenu] = useState(null);
|
|
522
677
|
|
|
@@ -567,6 +722,14 @@ function LeftPanel({ files, activeFile, onFileClick }) {
|
|
|
567
722
|
const isExpanded = expandedDirs[item.path];
|
|
568
723
|
const indent = 8 + depth * 16;
|
|
569
724
|
|
|
725
|
+
// Get file git status
|
|
726
|
+
let fileGitStatus = null;
|
|
727
|
+
if (!isDir && gitInfo.status) {
|
|
728
|
+
fileGitStatus = gitInfo.status.staged?.find(f => f.path === item.path) ||
|
|
729
|
+
gitInfo.status.unstaged?.find(f => f.path === item.path) ||
|
|
730
|
+
gitInfo.status.untracked?.find(f => f.path === item.path);
|
|
731
|
+
}
|
|
732
|
+
|
|
570
733
|
return (
|
|
571
734
|
<React.Fragment key={item.path}>
|
|
572
735
|
<div
|
|
@@ -576,6 +739,11 @@ function LeftPanel({ files, activeFile, onFileClick }) {
|
|
|
576
739
|
onContextMenu={(e) => handleContextMenu(e, item)}
|
|
577
740
|
style={{ paddingLeft: `${indent}px` }}
|
|
578
741
|
>
|
|
742
|
+
{fileGitStatus && (
|
|
743
|
+
<span className={`file-git-badge ${fileGitStatus.indexStatus !== '?' ? 'staged' : 'wt'}`}>
|
|
744
|
+
{fileGitStatus.indexStatus !== '?' ? fileGitStatus.indexStatus : fileGitStatus.workTreeStatus}
|
|
745
|
+
</span>
|
|
746
|
+
)}
|
|
579
747
|
<span className="file-icon">
|
|
580
748
|
{isDir ? (isExpanded ? '📂' : '📁') : getFileIcon(item.type)}
|
|
581
749
|
</span>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useMemo, useRef } from 'react';
|
|
2
2
|
import CodeMirror from '@uiw/react-codemirror';
|
|
3
|
-
import { EditorView, Decoration, ViewPlugin
|
|
3
|
+
import { EditorView, Decoration, ViewPlugin } from '@codemirror/view';
|
|
4
4
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
5
5
|
import { tags as t } from '@lezer/highlight';
|
|
6
6
|
import { RangeSetBuilder } from '@codemirror/state';
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
|
|
3
|
+
class DefaultErrorFallback extends Component {
|
|
4
|
+
render() {
|
|
5
|
+
const { error, onReload, onReset } = this.props;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div style={{
|
|
9
|
+
height: '100vh',
|
|
10
|
+
display: 'flex',
|
|
11
|
+
flexDirection: 'column',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
justifyContent: 'center',
|
|
14
|
+
background: '#1e1e1e',
|
|
15
|
+
color: '#e0e0e0',
|
|
16
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
17
|
+
padding: '20px',
|
|
18
|
+
}}>
|
|
19
|
+
<div style={{
|
|
20
|
+
maxWidth: '600px',
|
|
21
|
+
padding: '32px',
|
|
22
|
+
background: '#252526',
|
|
23
|
+
borderRadius: '8px',
|
|
24
|
+
border: '1px solid #3c3c3c',
|
|
25
|
+
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
|
|
26
|
+
textAlign: 'center',
|
|
27
|
+
}}>
|
|
28
|
+
<div style={{
|
|
29
|
+
fontSize: '48px',
|
|
30
|
+
marginBottom: '16px',
|
|
31
|
+
}}>
|
|
32
|
+
<span style={{ color: '#f85149' }}>!</span>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<h1 style={{
|
|
36
|
+
fontSize: '24px',
|
|
37
|
+
fontWeight: 600,
|
|
38
|
+
marginBottom: '8px',
|
|
39
|
+
color: '#ffffff',
|
|
40
|
+
}}>
|
|
41
|
+
应用程序出错
|
|
42
|
+
</h1>
|
|
43
|
+
|
|
44
|
+
<p style={{
|
|
45
|
+
fontSize: '14px',
|
|
46
|
+
color: '#858585',
|
|
47
|
+
marginBottom: '24px',
|
|
48
|
+
}}>
|
|
49
|
+
抱歉,应用程序遇到了一个错误。请尝试刷新页面或重置应用状态。
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
{error && (
|
|
53
|
+
<details style={{
|
|
54
|
+
marginBottom: '24px',
|
|
55
|
+
padding: '12px',
|
|
56
|
+
background: '#1e1e1e',
|
|
57
|
+
borderRadius: '4px',
|
|
58
|
+
textAlign: 'left',
|
|
59
|
+
}}>
|
|
60
|
+
<summary style={{
|
|
61
|
+
cursor: 'pointer',
|
|
62
|
+
fontSize: '13px',
|
|
63
|
+
color: '#e0e0e0',
|
|
64
|
+
fontWeight: 500,
|
|
65
|
+
}}>
|
|
66
|
+
错误详情
|
|
67
|
+
</summary>
|
|
68
|
+
<pre style={{
|
|
69
|
+
marginTop: '12px',
|
|
70
|
+
padding: '12px',
|
|
71
|
+
background: '#0a0a0a',
|
|
72
|
+
borderRadius: '4px',
|
|
73
|
+
fontSize: '12px',
|
|
74
|
+
color: '#f48771',
|
|
75
|
+
overflow: 'auto',
|
|
76
|
+
maxHeight: '200px',
|
|
77
|
+
fontFamily: '"SF Mono", Consolas, monospace',
|
|
78
|
+
}}>
|
|
79
|
+
{error.toString()}
|
|
80
|
+
{error.stack && `\n\n${error.stack}`}
|
|
81
|
+
</pre>
|
|
82
|
+
</details>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div style={{
|
|
86
|
+
display: 'flex',
|
|
87
|
+
gap: '12px',
|
|
88
|
+
justifyContent: 'center',
|
|
89
|
+
}}>
|
|
90
|
+
<button
|
|
91
|
+
onClick={onReset}
|
|
92
|
+
style={{
|
|
93
|
+
padding: '10px 20px',
|
|
94
|
+
background: '#3c3c3c',
|
|
95
|
+
border: '1px solid #4c4c4c',
|
|
96
|
+
borderRadius: '4px',
|
|
97
|
+
color: '#cccccc',
|
|
98
|
+
fontSize: '14px',
|
|
99
|
+
cursor: 'pointer',
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
重置应用
|
|
103
|
+
</button>
|
|
104
|
+
<button
|
|
105
|
+
onClick={onReload}
|
|
106
|
+
style={{
|
|
107
|
+
padding: '10px 20px',
|
|
108
|
+
background: '#0078d4',
|
|
109
|
+
border: 'none',
|
|
110
|
+
borderRadius: '4px',
|
|
111
|
+
color: '#ffffff',
|
|
112
|
+
fontSize: '14px',
|
|
113
|
+
cursor: 'pointer',
|
|
114
|
+
fontWeight: 500,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
刷新页面
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class ErrorBoundary extends Component {
|
|
127
|
+
constructor(props) {
|
|
128
|
+
super(props);
|
|
129
|
+
this.state = {
|
|
130
|
+
hasError: false,
|
|
131
|
+
error: null,
|
|
132
|
+
errorInfo: null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static getDerivedStateFromError(error) {
|
|
137
|
+
return { hasError: true, error };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
componentDidCatch(error, errorInfo) {
|
|
141
|
+
this.setState({ errorInfo });
|
|
142
|
+
this.props.onError?.(error, errorInfo);
|
|
143
|
+
console.error('[ErrorBoundary]', error, errorInfo);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
handleReload = () => {
|
|
147
|
+
this.setState({ hasError: false, error: null, errorInfo: null });
|
|
148
|
+
window.location.reload();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
handleReset = () => {
|
|
152
|
+
this.setState({ hasError: false, error: null, errorInfo: null });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
render() {
|
|
156
|
+
if (this.state.hasError) {
|
|
157
|
+
if (this.props.fallback) {
|
|
158
|
+
return this.props.fallback;
|
|
159
|
+
}
|
|
160
|
+
return (
|
|
161
|
+
<DefaultErrorFallback
|
|
162
|
+
error={this.state.error}
|
|
163
|
+
errorInfo={this.state.errorInfo}
|
|
164
|
+
onReload={this.handleReload}
|
|
165
|
+
onReset={this.handleReset}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return this.props.children;
|
|
171
|
+
}
|
|
172
|
+
}
|