Profile
158 | 177 |Authentication
180 |The authentication uses your local SSH agent.
181 |├── .compilerc ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── css │ └── index.css ├── html │ └── index.html ├── less │ └── index.less └── themes │ ├── dark.json │ └── light.json ├── package.json ├── src ├── main │ ├── main.ts │ └── menu.ts ├── renderer │ ├── components │ │ ├── app.tsx │ │ ├── binary-patch-viewer.tsx │ │ ├── clone-repo-dialog.tsx │ │ ├── commit-item.tsx │ │ ├── commit-list.tsx │ │ ├── commit-viewer.tsx │ │ ├── conflict-viewer.tsx │ │ ├── create-branch-dialog.tsx │ │ ├── graph-canvas.tsx │ │ ├── graph-viewer.tsx │ │ ├── image-patch-viewer.tsx │ │ ├── index-item.tsx │ │ ├── index-viewer.tsx │ │ ├── init-repo-dialog.tsx │ │ ├── input-dialog.tsx │ │ ├── loading-screen.tsx │ │ ├── make-modal.tsx │ │ ├── notification-item.tsx │ │ ├── notification-queue.tsx │ │ ├── patch-item.tsx │ │ ├── patch-list.tsx │ │ ├── patch-viewer.tsx │ │ ├── preferences-dialog.tsx │ │ ├── reference-badge.tsx │ │ ├── reference-explorer.tsx │ │ ├── reference-item.tsx │ │ ├── reference-list.tsx │ │ ├── repo-dashboard.tsx │ │ ├── spinner-button.tsx │ │ ├── splitter.tsx │ │ ├── stash-item.tsx │ │ ├── stash-list.tsx │ │ ├── text-patch-viewer.tsx │ │ ├── toolbar.tsx │ │ └── welcome-dashboard.tsx │ ├── helpers │ │ ├── commit-context-menu.ts │ │ ├── commit-graph.ts │ │ ├── conflict-parser.ts │ │ ├── make-cancellable.ts │ │ ├── open-create-branch-window.ts │ │ ├── open-in-editor.ts │ │ ├── patch-comparison.ts │ │ ├── reference-context-menu.ts │ │ ├── repo-wrapper.ts │ │ └── stash-context-menu.ts │ └── index.tsx ├── resize_observer.d.ts └── shared │ ├── settings.ts │ └── theme-manager.ts ├── tsconfig.json └── tslint.json /.compilerc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "text/typescript": { 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "declaration": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "suppressImplicitAnyIndexErrors": true, 11 | "strictNullChecks": true, 12 | "noUnusedLocals": true, 13 | "noImplicitThis": true, 14 | "noUnusedParameters": true, 15 | "inlineSourceMap": true, 16 | "inlineSources": true, 17 | "importHelpers": true, 18 | "noEmitHelpers": true, 19 | "experimentalDecorators": true, 20 | "target": "es2015", 21 | "module": "commonjs", 22 | "jsx": "react" 23 | } 24 | }, 25 | "production": { 26 | "text/typescript": { 27 | "removeComments": false, 28 | "preserveConstEnums": true, 29 | "declaration": true, 30 | "noImplicitAny": true, 31 | "noImplicitReturns": true, 32 | "suppressImplicitAnyIndexErrors": true, 33 | "strictNullChecks": true, 34 | "noUnusedLocals": true, 35 | "noImplicitThis": true, 36 | "noUnusedParameters": true, 37 | "sourceMap": false, 38 | "importHelpers": true, 39 | "noEmitHelpers": true, 40 | "experimentalDecorators": true, 41 | "target": "es2015", 42 | "jsx": "react" 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # npm package-lock 64 | package-lock\.json 65 | 66 | # Electron-forge 67 | out/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitamine 2 | 3 | gitamine is a modern graphical user interface for Git. It is open-source, multiplatform and easy to use. 4 | 5 | The main features of gitamine are: 6 | 7 | * Beautiful and readable commit graph 8 | * Syntax highlighting 9 | * Themeable (light and dark modes available) 10 | * Automatic refresh 11 | * No account required and no telemetry 12 | 13 | gitamine is powered by awesome technologies including: 14 | 15 | * [TypeScript](https://www.typescriptlang.org/) 16 | * [Node.js](https://nodejs.org/) 17 | * [Electron](https://electronjs.org/) 18 | * [React](https://reactjs.org/) 19 | * [Monaco](https://github.com/Microsoft/monaco-editor) 20 | * [libgit2](https://libgit2.org/) and [NodeGit](https://www.nodegit.org/) 21 | * [chokidar](https://github.com/paulmillr/chokidar) 22 | 23 | If you want to know more about the commit graph drawing algorithm, you can read this [article](https://pvigier.github.io/2019/05/06/commit-graph-drawing-algorithms.html). 24 | 25 | ## Downloads 26 | 27 | You can download the latest version of gitamine [here](https://github.com/pvigier/gitamine/releases/latest). 28 | 29 | See this [page](https://github.com/pvigier/gitamine/wiki/How-to-install) for detailed instruction on how to install gitamine. 30 | 31 | ## Contributing 32 | 33 | Here are some ways, you can contribute to gitamine: 34 | 35 | * Found a bug, please create an issue, I will try to fix it as quickly as possible. 36 | * Want to fix an issue or add a feature yourself, do a pull request. 37 | * If you are a designer, gitamine needs an application icon and some icons for the user interface, you are more than welcome. 38 | * If you want to become an active maintainer, please contact me. 39 | 40 | ## Screenshots 41 | 42 |  43 |  44 | 45 | ## License 46 | 47 | gitamine is distributed under the [GNU GENERAL PUBLIC LICENSE version 3](https://www.gnu.org/licenses/gpl-3.0.en.html). 48 | 49 | You are free to use gitamine for non-commercial and commercial projects without any restriction. 50 | -------------------------------------------------------------------------------- /assets/css/index.css: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | :root { 3 | --canvas-width: 32px; 4 | /* Theme's variables (default value should correspond to the default theme) */ 5 | --background-color: #ffffff; 6 | --toolbar-color: #f5f5f5; 7 | --list-background-color: #ebebeb; 8 | --hover-color: #e1e1e1; 9 | --selected-color: #d7d7d7; 10 | --splitter-color: #b9b9b9; 11 | --border-color: #e6e6e6; 12 | --font-color: #000000; 13 | --font-famliy: Arial, Helvetica, sans-serif; 14 | --font-size: 14px; 15 | --link-color: #0000ee; 16 | --button-font-color: #ffffff; 17 | --green-button-border-color: #006400; 18 | --green-button-background-color: #008000; 19 | --red-button-border-color: #8b0000; 20 | --red-button-background-color: #a70000; 21 | --yellow-button-border-color: #b8860b; 22 | --yellow-button-background-color: #daa520; 23 | --notification-font-color: #ffffff; 24 | --info-notification-border-color: #006400; 25 | --info-notification-background-color: #008000; 26 | --error-notification-border-color: #8b0000; 27 | --error-notification-background-color: #a70000; 28 | --reference-section-background-color: #c8c8c8; 29 | --spinner-color: #1e90ff; 30 | --spinner-border: #f3f3f3; 31 | } 32 | /* Body */ 33 | html, 34 | body { 35 | margin: 0; 36 | overflow: hidden; 37 | background-color: var(--background-color); 38 | color: var(--font-color); 39 | font-family: var(--font-famliy); 40 | font-size: var(--font-size); 41 | user-select: none; 42 | } 43 | div#container { 44 | height: 100vh; 45 | width: 100vw; 46 | } 47 | div#app { 48 | height: 100%; 49 | } 50 | /* General */ 51 | h1 { 52 | font-size: 20px; 53 | } 54 | h2 { 55 | font-size: 16px; 56 | } 57 | h3 { 58 | font-size: 15px; 59 | } 60 | button { 61 | margin: 4px 8px; 62 | } 63 | a { 64 | color: var(--link-color); 65 | } 66 | /* Repo dashboard */ 67 | div.repo-dashboard { 68 | height: 100%; 69 | display: flex; 70 | flex-direction: column; 71 | } 72 | /* Repo toolbar */ 73 | div.repo-toolbar { 74 | height: 50px; 75 | display: flex; 76 | text-align: center; 77 | background-color: var(--toolbar-color); 78 | box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.2); 79 | z-index: 3; 80 | } 81 | div.repo-toolbar div.repo-title { 82 | display: table; 83 | height: 100%; 84 | margin-right: 25px; 85 | background-color: var(--list-background-color); 86 | padding: 0px 16px; 87 | } 88 | div.repo-toolbar div.repo-title h1 { 89 | display: table-cell; 90 | vertical-align: middle; 91 | margin: 0px; 92 | font-size: 16px; 93 | } 94 | div.repo-toolbar div.separator { 95 | border-width: 25px 0px 25px 20px; 96 | border-style: solid; 97 | border-color: transparent transparent transparent var(--list-background-color); 98 | margin-left: -25px; 99 | } 100 | div.repo-toolbar div.toolbar-buttons { 101 | flex: 1; 102 | display: flex; 103 | justify-content: center; 104 | padding: 4px 0px; 105 | } 106 | div.repo-toolbar div.toolbar-buttons button { 107 | margin: 0px 2px; 108 | padding: 0px; 109 | width: 48px; 110 | } 111 | div.repo-toolbar div.toolbar-buttons button div.spinner { 112 | border: 3px solid var(--spinner-border); 113 | border-top: 3px solid var(--spinner-color); 114 | border-radius: 50%; 115 | width: 15px; 116 | height: 15px; 117 | animation: spin 2s linear infinite; 118 | margin: auto; 119 | } 120 | @keyframes spin { 121 | 0% { 122 | transform: rotate(0deg); 123 | } 124 | 100% { 125 | transform: rotate(360deg); 126 | } 127 | } 128 | /* Repo content */ 129 | div.repo-content { 130 | flex: 1; 131 | display: flex; 132 | } 133 | /* Loading screen */ 134 | div.loading-screen { 135 | flex: 1; 136 | min-width: 300px; 137 | display: flex; 138 | flex-direction: column; 139 | justify-content: center; 140 | align-items: center; 141 | } 142 | div.loading-screen div.spinner { 143 | border: 16px solid var(--spinner-border); 144 | border-top: 16px solid var(--spinner-color); 145 | border-radius: 50%; 146 | width: 80px; 147 | height: 80px; 148 | animation: spin 2s linear infinite; 149 | } 150 | @keyframes spin { 151 | 0% { 152 | transform: rotate(0deg); 153 | } 154 | 100% { 155 | transform: rotate(360deg); 156 | } 157 | } 158 | /* Reference explorer */ 159 | div.reference-explorer { 160 | width: 200px; 161 | padding-top: 8px; 162 | } 163 | div.reference-explorer > div { 164 | overflow-y: auto; 165 | height: 100%; 166 | } 167 | div.reference-explorer li { 168 | list-style: none; 169 | text-overflow: ellipsis; 170 | overflow: hidden; 171 | white-space: nowrap; 172 | background: var(--list-background-color); 173 | padding: 2px 4px 2px 16px; 174 | font-size: 13px; 175 | } 176 | div.reference-explorer li:hover { 177 | background: var(--hover-color); 178 | } 179 | div.reference-explorer li.selected { 180 | background: var(--selected-color); 181 | } 182 | div.reference-explorer ul { 183 | margin: 0px; 184 | padding: 0px; 185 | } 186 | div.reference-explorer ul.hidden { 187 | display: none; 188 | } 189 | div.reference-explorer h3 { 190 | margin: 0px; 191 | padding: 4px; 192 | background: var(--reference-section-background-color); 193 | display: flex; 194 | justify-content: space-between; 195 | white-space: nowrap; 196 | } 197 | div.reference-explorer h3 span { 198 | padding: 0px 4px; 199 | } 200 | /* Graph viewer */ 201 | div.graph-viewer { 202 | flex: 1; 203 | min-width: 300px; 204 | display: flex; 205 | } 206 | div.graph-viewer div.graph-container { 207 | position: relative; 208 | flex: 1; 209 | min-width: calc(var(--canvas-width) + 100px); 210 | } 211 | /* Commit graph */ 212 | div.commit-graph { 213 | min-width: 32px; 214 | width: var(--canvas-width); 215 | height: 100%; 216 | overflow-x: scroll; 217 | overflow-y: hidden; 218 | } 219 | div.commit-graph canvas { 220 | position: sticky; 221 | margin-left: auto; 222 | padding: 0px 10px; 223 | display: block; 224 | z-index: 2; 225 | pointer-events: none; 226 | } 227 | div.commit-graph .splitter { 228 | position: absolute; 229 | top: 0px; 230 | left: calc(var(--canvas-width)); 231 | height: 100%; 232 | z-index: 3; 233 | border: 1px dashed var(--splitter-color); 234 | } 235 | /* Commit list */ 236 | div.commit-list { 237 | width: 100%; 238 | height: calc(100% - 15px); 239 | position: absolute; 240 | top: 0px; 241 | z-index: 1; 242 | overflow-y: scroll; 243 | text-align: left; 244 | } 245 | div.commit-list ul { 246 | margin: 0px; 247 | padding-left: 0px; 248 | list-style: none; 249 | line-height: 22px; 250 | background-color: var(--list-background-color); 251 | min-height: 100%; 252 | } 253 | div.commit-list ul li { 254 | white-space: nowrap; 255 | overflow: hidden; 256 | text-overflow: ellipsis; 257 | height: 22px; 258 | padding-left: calc(var(--canvas-width) + 16px); 259 | padding-top: 3px; 260 | padding-bottom: 3px; 261 | font-size: 13px; 262 | } 263 | div.commit-list ul li:hover { 264 | background: var(--hover-color); 265 | } 266 | div.commit-list ul li.selected-commit { 267 | background: var(--selected-color); 268 | } 269 | /* Reference badge */ 270 | div.commit-list li span.reference { 271 | border-radius: 5px; 272 | padding: 2px; 273 | margin-right: 2px; 274 | background-color: var(--branch-color); 275 | color: white; 276 | user-select: none; 277 | filter: brightness(0.8); 278 | } 279 | div.commit-list li span.reference.tag { 280 | border-radius: 0px; 281 | } 282 | div.commit-list li span.reference:hover { 283 | filter: brightness(1); 284 | } 285 | div.commit-list li span.reference.selected { 286 | filter: brightness(1); 287 | } 288 | /* Patch viewer */ 289 | div.patch-viewer { 290 | flex: 1; 291 | min-width: 300px; 292 | z-index: 1; 293 | background: var(--background-color); 294 | display: flex; 295 | flex-direction: column; 296 | } 297 | div.patch-viewer div.patch-header { 298 | height: 30px; 299 | display: flex; 300 | justify-content: space-between; 301 | align-items: center; 302 | flex-shrink: 0; 303 | } 304 | div.patch-viewer div.patch-header h2 { 305 | margin: 0px; 306 | padding: 6px 8px; 307 | user-select: text; 308 | } 309 | div.patch-viewer div.patch-toolbar { 310 | height: 30px; 311 | display: flex; 312 | justify-content: space-between; 313 | align-items: center; 314 | } 315 | div.patch-viewer div.patch-toolbar button { 316 | margin: 4px; 317 | } 318 | div.patch-viewer div.patch-editor { 319 | height: calc(100% - 30px - 30px); 320 | align-items: center; 321 | } 322 | div.patch-viewer div.patch-editor.hidden { 323 | visibility: hidden; 324 | } 325 | div.patch-viewer div.image-viewer { 326 | overflow: auto; 327 | padding: 0px 8px; 328 | } 329 | div.patch-viewer div.binary-viewer { 330 | padding: 0px 8px; 331 | } 332 | /* Hunk widget */ 333 | div.hunk-widget { 334 | width: 100%; 335 | } 336 | div.hunk-widget > div { 337 | display: flex; 338 | position: absolute; 339 | bottom: 0px; 340 | width: 100%; 341 | border-bottom: 1px solid; 342 | } 343 | div.hunk-widget > div > div { 344 | position: absolute; 345 | right: 18px; 346 | bottom: 2px; 347 | } 348 | div.hunk-widget p { 349 | margin: 0px; 350 | } 351 | div.hunk-widget button { 352 | margin: 0px 2px; 353 | } 354 | /* Conflict widget */ 355 | div.conflict-widget { 356 | width: 100%; 357 | } 358 | div.conflict-widget > div { 359 | display: flex; 360 | position: absolute; 361 | bottom: 0px; 362 | width: 100%; 363 | border-bottom: 1px solid; 364 | } 365 | div.conflict-widget > div > div { 366 | position: absolute; 367 | right: 18px; 368 | bottom: 2px; 369 | } 370 | div.conflict-widget p { 371 | margin: 0px 8px; 372 | } 373 | div.conflict-widget button { 374 | margin: 0px 2px; 375 | } 376 | /* Margin buttons */ 377 | .stage-line-button { 378 | width: 19px; 379 | height: 19px; 380 | text-align: center; 381 | color: white; 382 | font-family: 'Courier New', Courier, monospace; 383 | font-weight: bold; 384 | border-radius: 5px; 385 | } 386 | .stage-line-button * { 387 | font-family: var(--font-famliy); 388 | font-weight: normal; 389 | } 390 | .stage-line-button:hover { 391 | background: green; 392 | } 393 | .stage-line-button:hover::before { 394 | content: '+'; 395 | } 396 | .unstage-line-button { 397 | width: 19px; 398 | height: 19px; 399 | text-align: center; 400 | color: white; 401 | font-family: 'Courier New', Courier, monospace; 402 | font-weight: bold; 403 | border-radius: 5px; 404 | } 405 | .unstage-line-button * { 406 | font-family: var(--font-famliy); 407 | font-weight: normal; 408 | } 409 | .unstage-line-button:hover { 410 | background: red; 411 | } 412 | .unstage-line-button:hover::before { 413 | content: '-'; 414 | } 415 | /* Inline decorations */ 416 | .conflict-start { 417 | background: lightgreen; 418 | } 419 | .conflict-current { 420 | background: #bcf5bc; 421 | } 422 | .conflict-end { 423 | background: lightblue; 424 | } 425 | .conflict-incoming { 426 | background: #d4ebf2; 427 | } 428 | /* Splitter */ 429 | div.splitter { 430 | border: 1.5px solid var(--splitter-color); 431 | cursor: col-resize; 432 | z-index: 1; 433 | } 434 | /* Commit viewer */ 435 | .commit-viewer { 436 | width: 300px; 437 | min-width: 300px; 438 | display: flex; 439 | flex-direction: column; 440 | background: var(--background-color); 441 | z-index: 4; 442 | user-select: text; 443 | } 444 | .commit-viewer h3 { 445 | margin: 4px 0; 446 | text-align: center; 447 | font-size: var(--font-size); 448 | font-weight: normal; 449 | } 450 | .commit-viewer div.commit-message { 451 | max-height: 100px; 452 | overflow-y: auto; 453 | background: var(--list-background-color); 454 | border: 1px solid var(--border-color); 455 | margin: 8px; 456 | padding: 8px; 457 | } 458 | .commit-viewer div.commit-message h2 { 459 | margin: 0px; 460 | } 461 | .commit-viewer div.commit-message pre { 462 | font-size: 13px; 463 | font-family: var(--font-famliy); 464 | margin: 8px 0px 0px 0px; 465 | } 466 | .commit-viewer p { 467 | margin: 4px 8px; 468 | } 469 | .commit-viewer .sha-button { 470 | border-bottom: 1px dotted var(--font-color); 471 | } 472 | .commit-viewer .navigate-button { 473 | cursor: pointer; 474 | } 475 | .commit-viewer div.section-header { 476 | display: flex; 477 | position: relative; 478 | } 479 | .commit-viewer div.section-header button { 480 | position: absolute; 481 | right: 0px; 482 | } 483 | .commit-viewer div.section-header .amend-container { 484 | display: flex; 485 | position: absolute; 486 | right: 0px; 487 | margin: 4px 8px; 488 | } 489 | .commit-viewer > input { 490 | margin: 4px 8px; 491 | } 492 | .commit-viewer > textarea { 493 | margin: 4px 8px; 494 | resize: none; 495 | font-family: var(--font-famliy); 496 | } 497 | /* Index Viewer */ 498 | .index-viewer { 499 | user-select: none; 500 | } 501 | /* Patch list */ 502 | ul.patch-list { 503 | flex: 1; 504 | margin: 8px; 505 | border: 1px solid var(--border-color); 506 | padding-left: 0px; 507 | list-style: none; 508 | overflow-y: auto; 509 | background-color: var(--list-background-color); 510 | } 511 | ul.patch-list li { 512 | padding: 4px; 513 | display: flex; 514 | position: relative; 515 | user-select: none; 516 | } 517 | ul.patch-list li:hover { 518 | background: var(--hover-color); 519 | } 520 | ul.patch-list li.selected-patch { 521 | background: var(--selected-color); 522 | } 523 | ul.patch-list li span.icon { 524 | padding-right: 4px; 525 | } 526 | ul.patch-list li div.buttons { 527 | position: absolute; 528 | width: 100%; 529 | height: 100%; 530 | left: 0; 531 | top: 0; 532 | text-align: right; 533 | line-height: 25px; 534 | } 535 | ul.patch-list li div.buttons button { 536 | display: none; 537 | margin-right: 2px; 538 | padding: 0px 2px; 539 | } 540 | ul.patch-list li div.buttons:hover button { 541 | display: inline; 542 | } 543 | /* Patch icons */ 544 | span.patch-add::before { 545 | font-family: 'Courier New', Courier, monospace; 546 | font-weight: bold; 547 | content: '+'; 548 | color: green; 549 | } 550 | span.patch-delete::before { 551 | font-family: 'Courier New', Courier, monospace; 552 | font-weight: bold; 553 | content: '-'; 554 | color: red; 555 | } 556 | span.patch-modify::before { 557 | font-family: 'Courier New', Courier, monospace; 558 | font-weight: bold; 559 | content: '\2026'; 560 | color: orange; 561 | } 562 | span.patch-rename::before { 563 | font-family: 'Courier New', Courier, monospace; 564 | font-weight: bold; 565 | content: '/'; 566 | color: dodgerblue; 567 | } 568 | span.patch-conflict::before { 569 | font-family: 'Courier New', Courier, monospace; 570 | font-weight: bold; 571 | content: '!'; 572 | color: orange; 573 | } 574 | /* Ellipsis middle */ 575 | .ellipsis-middle { 576 | display: flex; 577 | overflow: hidden; 578 | } 579 | .ellipsis-middle .left { 580 | text-overflow: ellipsis; 581 | overflow: hidden; 582 | white-space: nowrap; 583 | } 584 | .ellipsis-middle .right { 585 | max-width: 100%; 586 | flex-shrink: 0; 587 | text-overflow: ellipsis; 588 | overflow: hidden; 589 | white-space: nowrap; 590 | } 591 | /* Notifications */ 592 | ul.notification-queue { 593 | position: absolute; 594 | top: 0; 595 | left: calc(100% - 400px); 596 | width: 800px; 597 | z-index: 5; 598 | list-style: none; 599 | margin: 16px 0px; 600 | padding: 0px; 601 | } 602 | ul.notification-queue li { 603 | width: calc(50% - 2 * (16px + 1px)); 604 | padding: 16px; 605 | margin: 8px 0px; 606 | margin-left: 50%; 607 | transition: margin-left 1s 0s; 608 | border: 1px solid var(--border-color); 609 | background: var(--background-color); 610 | box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.2); 611 | } 612 | ul.notification-queue li.shown { 613 | margin-left: 0px; 614 | } 615 | ul.notification-queue li:hover { 616 | background: var(--hover-color); 617 | } 618 | ul.notification-queue li.information { 619 | color: var(--notification-font-color); 620 | background-color: var(--info-notification-background-color); 621 | border-color: var(--info-notification-border-color); 622 | } 623 | ul.notification-queue li.information:hover { 624 | background-color: var(--info-notification-background-color); 625 | border-color: var(--info-notification-background-color); 626 | } 627 | ul.notification-queue li.information:active { 628 | background-color: var(--info-notification-border-color); 629 | border-color: var(--info-notification-background-color); 630 | } 631 | ul.notification-queue li.error { 632 | color: var(--notification-font-color); 633 | background-color: var(--error-notification-background-color); 634 | border-color: var(--error-notification-border-color); 635 | } 636 | ul.notification-queue li.error:hover { 637 | background-color: var(--error-notification-background-color); 638 | border-color: var(--error-notification-background-color); 639 | } 640 | ul.notification-queue li.error:active { 641 | background-color: var(--error-notification-border-color); 642 | border-color: var(--error-notification-background-color); 643 | } 644 | /* Welcome dashboard */ 645 | div.welcome-dashboard { 646 | margin-top: 100px; 647 | text-align: center; 648 | } 649 | div.welcome-dashboard h1 { 650 | margin-bottom: 20px; 651 | } 652 | div.welcome-dashboard div.actions { 653 | width: 600px; 654 | margin: auto; 655 | display: flex; 656 | } 657 | div.welcome-dashboard div.actions div.action { 658 | width: 174px; 659 | background-color: var(--list-background-color); 660 | margin: 8px; 661 | padding: 4px; 662 | border: 1px solid var(--border-color); 663 | border-radius: 3px; 664 | overflow-wrap: break-word; 665 | } 666 | div.welcome-dashboard div.actions div.action p { 667 | text-align: left; 668 | font-size: 12px; 669 | } 670 | div.welcome-dashboard div.actions div.action:hover { 671 | background-color: var(--hover-color); 672 | } 673 | div.welcome-dashboard div.actions div.action:active { 674 | background-color: var(--selected-color); 675 | } 676 | /* Tooltips */ 677 | .tooltip-bottom { 678 | position: relative; 679 | } 680 | .tooltip-bottom .tooltip-text { 681 | visibility: hidden; 682 | background-color: black; 683 | color: #fff; 684 | border-radius: 5px; 685 | padding: 5px; 686 | position: absolute; 687 | z-index: 1; 688 | left: 50%; 689 | top: calc(100% + 8px); 690 | transform: translateX(-50%); 691 | } 692 | .tooltip-bottom .tooltip-text::after { 693 | content: ""; 694 | position: absolute; 695 | bottom: 100%; 696 | left: 50%; 697 | margin-left: -5px; 698 | border-width: 5px; 699 | border-style: solid; 700 | border-color: transparent transparent black transparent; 701 | } 702 | .tooltip-bottom:hover .tooltip-text { 703 | visibility: visible; 704 | } 705 | .tooltip-right { 706 | position: relative; 707 | } 708 | .tooltip-right .tooltip-text { 709 | visibility: hidden; 710 | background-color: black; 711 | color: #fff; 712 | border-radius: 5px; 713 | padding: 5px; 714 | position: absolute; 715 | z-index: 1; 716 | top: 50%; 717 | left: calc(100% + 8px); 718 | transform: translateY(-50%); 719 | white-space: nowrap; 720 | } 721 | .tooltip-right .tooltip-text::after { 722 | content: ""; 723 | position: absolute; 724 | right: 100%; 725 | top: 50%; 726 | margin-top: -5px; 727 | border-width: 5px; 728 | border-style: solid; 729 | border-color: transparent black transparent transparent; 730 | } 731 | .tooltip-right:hover .tooltip-text { 732 | visibility: visible; 733 | } 734 | /* Buttons */ 735 | button { 736 | background-color: var(--list-background-color); 737 | border: 1px solid var(--border-color); 738 | border-radius: 3px; 739 | color: var(--font-color); 740 | outline: 0px; 741 | } 742 | button:hover { 743 | background-color: var(--hover-color); 744 | } 745 | button:active { 746 | background-color: var(--selected-color); 747 | } 748 | button:disabled { 749 | cursor: not-allowed; 750 | } 751 | .green-button { 752 | color: var(--button-font-color); 753 | background-color: var(--green-button-background-color); 754 | border-color: var(--green-button-border-color); 755 | } 756 | .green-button:hover { 757 | background-color: var(--green-button-background-color); 758 | border-color: var(--green-button-background-color); 759 | } 760 | .green-button:active { 761 | background-color: var(--green-button-border-color); 762 | border-color: var(--green-button-background-color); 763 | } 764 | .red-button { 765 | color: var(--button-font-color); 766 | background-color: var(--red-button-background-color); 767 | border-color: var(--red-button-border-color); 768 | } 769 | .red-button:hover { 770 | background-color: var(--red-button-background-color); 771 | border-color: var(--red-button-background-color); 772 | } 773 | .red-button:active { 774 | background-color: var(--red-button-border-color); 775 | border-color: var(--red-button-background-color); 776 | } 777 | .yellow-button { 778 | color: var(--button-font-color); 779 | background-color: var(--yellow-button-background-color); 780 | border-color: var(--yellow-button-border-color); 781 | } 782 | .yellow-button:hover { 783 | background-color: var(--yellow-button-background-color); 784 | border-color: var(--yellow-button-background-color); 785 | } 786 | .yellow-button:active { 787 | background-color: var(--yellow-button-border-color); 788 | border-color: var(--yellow-button-background-color); 789 | } 790 | /* Modal windows */ 791 | div.modal-container { 792 | position: absolute; 793 | top: 0px; 794 | left: 0px; 795 | width: 100%; 796 | height: 100%; 797 | z-index: 4; 798 | display: flex; 799 | justify-content: center; 800 | align-items: center; 801 | background-color: rgba(0, 0, 0, 0.5); 802 | } 803 | div.modal-container div.modal-background { 804 | background-color: var(--background-color); 805 | } 806 | div.modal-container div.modal-background div.modal-header { 807 | position: relative; 808 | text-align: center; 809 | border-bottom: 1px solid var(--border-color); 810 | margin: 0px 8px; 811 | padding: 4px 0px; 812 | } 813 | div.modal-container div.modal-background div.modal-header h2 { 814 | margin: 0px; 815 | } 816 | div.modal-container div.modal-background div.modal-header button { 817 | position: absolute; 818 | top: 0px; 819 | right: 0px; 820 | margin: 4px 0px; 821 | } 822 | /* Modal forms */ 823 | form.modal-form { 824 | display: flex; 825 | flex-direction: column; 826 | margin: 10px; 827 | } 828 | form.modal-form div.field-container { 829 | display: flex; 830 | margin: 4px 0px; 831 | } 832 | form.modal-form div.field-container label { 833 | min-width: 140px; 834 | display: inline-block; 835 | text-align: right; 836 | margin-right: 10px; 837 | } 838 | form.modal-form div.field-container input[type="text"] { 839 | flex: 1; 840 | } 841 | form.modal-form div.field-container span#prefix { 842 | overflow: hidden; 843 | text-overflow: ellipsis; 844 | } 845 | form.modal-form div.button-container { 846 | width: 100%; 847 | margin: 4px 0px; 848 | } 849 | form.modal-form div.button-container button { 850 | width: 100%; 851 | margin: 0px; 852 | } 853 | /* Preferences */ 854 | div.preferences { 855 | width: 600px; 856 | height: 400px; 857 | display: flex; 858 | } 859 | div.preferences nav { 860 | width: 200px; 861 | } 862 | div.preferences nav ul { 863 | list-style: none; 864 | margin: 16px 0px; 865 | padding: 0px; 866 | } 867 | div.preferences nav ul li { 868 | padding: 5px 16px; 869 | } 870 | div.preferences nav ul li:hover { 871 | background-color: var(--hover-color); 872 | } 873 | div.preferences nav ul li.selected { 874 | background-color: var(--selected-color); 875 | } 876 | div.preferences main { 877 | flex-grow: 1; 878 | } 879 | div.preferences section { 880 | display: none; 881 | padding: 16px 8px; 882 | } 883 | div.preferences section.shown { 884 | display: block; 885 | } 886 | div.preferences section h1 { 887 | margin: 0px; 888 | border-bottom: 1px solid var(--border-color); 889 | margin-bottom: 16px; 890 | } 891 | div.preferences section div { 892 | margin: 4px 0px; 893 | } 894 | div.preferences section div label { 895 | margin-right: 4px; 896 | } 897 | -------------------------------------------------------------------------------- /assets/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |Binary file
8 |{body}: null} 127 |
By {author.name()} <{author.email()}>
129 |Authored {formatDate(authoredDate)}
130 |Last modified {formatDate(commit.date())}
131 |Parents: {this.createNavigationButtons(this.props.repo.parents.get(commit.sha())!)}
132 |Old file:
30 | {this.props.patch.oldFile().flags() & Git.Diff.FLAG.EXISTS ? 31 |New file:
34 | {this.props.patch.newFile().flags() & Git.Diff.FLAG.EXISTS ? 35 |Loading...
18 |= P & {title: string}; 8 | 9 | interface ComponentConstructor
{ 10 | new(props: P): React.PureComponent
; 11 | } 12 | 13 | export function makeModal
(Component: ComponentConstructor
) {
14 | return class extends React.PureComponent ) {
16 | super(props);
17 | this.handleKeyUp = this.handleKeyUp.bind(this);
18 | }
19 |
20 | handleKeyUp(event: React.KeyboardEvent The authentication uses your local SSH agent.
38 |
39 | {formattedPath.substr(0, i)}
40 | {formattedPath.substr(i)}
41 |
42 | {this.props.title}
33 |
36 |
56 | {notificationItems}
57 |
58 | );
59 | }
60 | }
--------------------------------------------------------------------------------
/src/renderer/components/patch-item.tsx:
--------------------------------------------------------------------------------
1 | import { remote, clipboard } from 'electron';
2 | import * as React from 'react';
3 | import * as Git from 'nodegit';
4 | import { RepoWrapper, PatchType } from '../helpers/repo-wrapper';
5 | import { openInEditor } from '../helpers/open-in-editor';
6 |
7 | export interface PatchItemProps {
8 | repo?: RepoWrapper;
9 | patch: Git.ConvenientPatch;
10 | type: PatchType;
11 | selected: boolean;
12 | onPatchSelect: (patch: Git.ConvenientPatch, ctrlKey: boolean, shiftKey: boolean) => void;
13 | }
14 |
15 | function getPatchIcon(patch: Git.ConvenientPatch) {
16 | if (patch.isAdded() || patch.isUntracked()) {
17 | return ;
18 | } else if (patch.isDeleted()) {
19 | return ;
20 | } else if (patch.isModified() || patch.isCopied()) {
21 | return ;
22 | } else if (patch.isRenamed()) {
23 | return ;
24 | } else if (patch.isConflicted()) {
25 | return ;
26 | } else {
27 | return null;
28 | }
29 | }
30 |
31 | export class PatchItem extends React.PureComponent
105 | {patchItems}
106 |
107 | );
108 | }
109 | }
--------------------------------------------------------------------------------
/src/renderer/components/patch-viewer.tsx:
--------------------------------------------------------------------------------
1 | import * as Path from 'path';
2 | import * as fs from 'fs';
3 | import * as React from 'react';
4 | import * as Git from 'nodegit';
5 | import fileType, { FileTypeResult } from 'file-type';
6 | import { isBinaryFile } from 'isbinaryfile';
7 | import { RepoWrapper, PatchType } from '../helpers/repo-wrapper'
8 | import { CancellablePromise, makeCancellable } from '../helpers/make-cancellable';
9 | import { arePatchesEqual } from '../helpers/patch-comparison';
10 | import { TextPatchViewer, TextPatchViewerOptions } from './text-patch-viewer';
11 | import { BinaryPatchViewer } from './binary-patch-viewer';
12 | import { ImagePatchViewer } from './image-patch-viewer';
13 | import { ConflictViewer } from './conflict-viewer';
14 |
15 | enum BlobType {
16 | Void,
17 | Text,
18 | Binary,
19 | Image
20 | }
21 |
22 | type Blob = [Buffer, BlobType];
23 |
24 | // Util
25 |
26 | function isImage(type: FileTypeResult | null) {
27 | return type !== null && type.mime.startsWith('image');
28 | }
29 |
30 | async function getBlob(repo: Git.Repository, file: Git.DiffFile) {
31 | let buffer: Buffer;
32 | try {
33 | const blob = await repo.getBlob(file.id());
34 | buffer = blob.content();
35 | } catch (e) {
36 | buffer = await new Promise
197 |
202 |
203 | Profile
158 |
177 | Authentication
180 | UI Preferences
184 |
192 | Editor Preferences
195 |
205 |
50 | {this.props.title}
51 | {this.state.hide ? '+' : '\u2212'}
52 |
53 |
54 | {referenceItems}
55 |
56 | >
57 | );
58 | }
59 | }
--------------------------------------------------------------------------------
/src/renderer/components/repo-dashboard.tsx:
--------------------------------------------------------------------------------
1 | import * as Path from 'path';
2 | import * as fs from 'fs';
3 | import * as chokidar from 'chokidar';
4 | import * as React from 'react';
5 | import * as Git from 'nodegit';
6 | import { GraphViewer } from './graph-viewer';
7 | import { CommitViewer } from './commit-viewer';
8 | import { IndexViewer } from './index-viewer';
9 | import { PatchViewer } from './patch-viewer';
10 | import { TextPatchViewerOptions } from './text-patch-viewer';
11 | import { Splitter } from './splitter';
12 | import { Toolbar } from './toolbar';
13 | import { LoadingScreen, LoadingState } from './loading-screen';
14 | import { InputDialogHandler } from './input-dialog';
15 | import { RepoWrapper, PatchType, RepoState } from '../helpers/repo-wrapper';
16 | import { CancellablePromise, makeCancellable } from '../helpers/make-cancellable';
17 |
18 | export interface RepoDashboardProps {
19 | repo: RepoWrapper;
20 | editorTheme: string;
21 | patchViewerOptions: TextPatchViewerOptions;
22 | onRepoClose: () => void;
23 | onCreateBranch: (commit: Git.Commit) => void;
24 | onOpenInputDialog: InputDialogHandler;
25 | }
26 |
27 | export interface RepoDashboardState {
28 | repoState: RepoState;
29 | loadingState: LoadingState;
30 | selectedCommit: Git.Commit | null;
31 | selectedPatch: Git.ConvenientPatch | null;
32 | patchType: PatchType;
33 | }
34 |
35 | export class RepoDashboard extends React.PureComponent
42 | Stashes
43 | {this.state.hide ? '+' : '\u2212'}
44 |
45 |
46 | {stashItems}
47 |
48 | >
49 | );
50 | }
51 | }
--------------------------------------------------------------------------------
/src/renderer/components/text-patch-viewer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as Git from 'nodegit';
3 | import { RepoWrapper, PatchType } from '../helpers/repo-wrapper'
4 | import { openInEditor } from '../helpers/open-in-editor';
5 | import { makeCancellable, CancellablePromise } from '../helpers/make-cancellable';
6 | import { arePatchesSimilar } from '../helpers/patch-comparison';
7 |
8 | // Load Monaco
9 |
10 | const loadMonaco = require('monaco-loader')
11 | let monaco: any;
12 | loadMonaco().then((m: any) => {
13 | monaco = m;
14 | });
15 |
16 | // TextPatchViewer
17 |
18 | const LINE_HEIGHT = 19; // To improve
19 |
20 | enum ViewMode {
21 | Hunk,
22 | Inline,
23 | Split
24 | }
25 |
26 | enum Editor {
27 | Original,
28 | Modified,
29 | Count
30 | }
31 |
32 | export interface TextPatchViewerOptions {
33 | fontSize: number;
34 | }
35 |
36 | export interface TextPatchViewerProps {
37 | repo: RepoWrapper;
38 | patch: Git.ConvenientPatch;
39 | oldString: string;
40 | newString: string;
41 | type: PatchType;
42 | editorTheme: string;
43 | options: TextPatchViewerOptions;
44 | }
45 |
46 | export class TextPatchViewer extends React.PureComponent{this.props.repo.name}
60 | {getRepoName(path)}
37 | Welcome to gitamine!
49 | Recently opened repo
50 | Actions
54 | Open a repo
57 | Init a repo
60 | Clone a repo
63 |