├── .DS_Store ├── .github └── ISSUE_TEMPLATE │ ├── Bug_report.md │ └── Feature_request.md ├── .gitignore ├── .npmrc ├── .travis.yml ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── app ├── .DS_Store ├── ci-integration │ └── appveyor.js ├── frontend │ ├── .angular-cli.json │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── e2e │ │ ├── app.e2e-spec.ts │ │ ├── app.po.ts │ │ └── tsconfig.e2e.json │ ├── karma-ci.conf.js │ ├── karma.conf.js │ ├── package-lock.json │ ├── package.json │ ├── protractor.conf.js │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── core │ │ │ │ ├── action-toolbar │ │ │ │ │ ├── action-toolbar.component.html │ │ │ │ │ ├── action-toolbar.component.scss │ │ │ │ │ ├── action-toolbar.component.spec.ts │ │ │ │ │ └── action-toolbar.component.ts │ │ │ │ ├── branch-item │ │ │ │ │ ├── branch-item.component.html │ │ │ │ │ ├── branch-item.component.scss │ │ │ │ │ ├── branch-item.component.spec.ts │ │ │ │ │ └── branch-item.component.ts │ │ │ │ ├── branch-list │ │ │ │ │ ├── branch-list.component.html │ │ │ │ │ ├── branch-list.component.scss │ │ │ │ │ ├── branch-list.component.spec.ts │ │ │ │ │ └── branch-list.component.ts │ │ │ │ ├── branch-viewer │ │ │ │ │ ├── branch-viewer.component.html │ │ │ │ │ ├── branch-viewer.component.scss │ │ │ │ │ ├── branch-viewer.component.spec.ts │ │ │ │ │ └── branch-viewer.component.ts │ │ │ │ ├── ci-console-output │ │ │ │ │ ├── ci-console-output.component.html │ │ │ │ │ ├── ci-console-output.component.scss │ │ │ │ │ ├── ci-console-output.component.spec.ts │ │ │ │ │ └── ci-console-output.component.ts │ │ │ │ ├── commit-detail-ci │ │ │ │ │ ├── commit-detail-ci.component.html │ │ │ │ │ ├── commit-detail-ci.component.scss │ │ │ │ │ ├── commit-detail-ci.component.spec.ts │ │ │ │ │ └── commit-detail-ci.component.ts │ │ │ │ ├── commit-detail-info │ │ │ │ │ ├── commit-detail-info.component.html │ │ │ │ │ ├── commit-detail-info.component.scss │ │ │ │ │ ├── commit-detail-info.component.spec.ts │ │ │ │ │ └── commit-detail-info.component.ts │ │ │ │ ├── commit-detail │ │ │ │ │ ├── commit-detail.component.html │ │ │ │ │ ├── commit-detail.component.scss │ │ │ │ │ ├── commit-detail.component.spec.ts │ │ │ │ │ └── commit-detail.component.ts │ │ │ │ ├── commit-file-list │ │ │ │ │ ├── commit-file-list.component.html │ │ │ │ │ ├── commit-file-list.component.scss │ │ │ │ │ ├── commit-file-list.component.spec.ts │ │ │ │ │ ├── commit-file-list.component.ts │ │ │ │ │ └── file-list-filter.ts │ │ │ │ ├── committer-card │ │ │ │ │ ├── committer-card.component.html │ │ │ │ │ ├── committer-card.component.scss │ │ │ │ │ ├── committer-card.component.spec.ts │ │ │ │ │ └── committer-card.component.ts │ │ │ │ ├── core.module.ts │ │ │ │ ├── create-branch-prompt │ │ │ │ │ ├── create-branch-prompt.component.html │ │ │ │ │ ├── create-branch-prompt.component.scss │ │ │ │ │ ├── create-branch-prompt.component.spec.ts │ │ │ │ │ └── create-branch-prompt.component.ts │ │ │ │ ├── d3 │ │ │ │ │ ├── d3.service.spec.ts │ │ │ │ │ ├── d3.service.ts │ │ │ │ │ └── models │ │ │ │ │ │ ├── color.ts │ │ │ │ │ │ ├── link.ts │ │ │ │ │ │ ├── node.ts │ │ │ │ │ │ └── subway-map.ts │ │ │ │ ├── enter-login-prompt │ │ │ │ │ ├── enter-login-prompt.component.html │ │ │ │ │ ├── enter-login-prompt.component.scss │ │ │ │ │ ├── enter-login-prompt.component.spec.ts │ │ │ │ │ └── enter-login-prompt.component.ts │ │ │ │ ├── external-file-viewer │ │ │ │ │ ├── external-file-viewer.component.html │ │ │ │ │ ├── external-file-viewer.component.scss │ │ │ │ │ ├── external-file-viewer.component.spec.ts │ │ │ │ │ └── external-file-viewer.component.ts │ │ │ │ ├── file-counts │ │ │ │ │ ├── file-counts.component.html │ │ │ │ │ ├── file-counts.component.scss │ │ │ │ │ ├── file-counts.component.spec.ts │ │ │ │ │ └── file-counts.component.ts │ │ │ │ ├── file-view-panel │ │ │ │ │ ├── file-view-panel.component.html │ │ │ │ │ ├── file-view-panel.component.scss │ │ │ │ │ ├── file-view-panel.component.spec.ts │ │ │ │ │ └── file-view-panel.component.ts │ │ │ │ ├── force-push-prompt │ │ │ │ │ ├── force-push-prompt.component.html │ │ │ │ │ ├── force-push-prompt.component.scss │ │ │ │ │ ├── force-push-prompt.component.spec.ts │ │ │ │ │ └── force-push-prompt.component.ts │ │ │ │ ├── git-view │ │ │ │ │ ├── git-view.component.css │ │ │ │ │ ├── git-view.component.html │ │ │ │ │ ├── git-view.component.spec.ts │ │ │ │ │ └── git-view.component.ts │ │ │ │ ├── map-separator │ │ │ │ │ ├── map-separator.component.html │ │ │ │ │ ├── map-separator.component.scss │ │ │ │ │ ├── map-separator.component.spec.ts │ │ │ │ │ └── map-separator.component.ts │ │ │ │ ├── mocks │ │ │ │ │ ├── mock-appveyor-ci-service.ts │ │ │ │ │ ├── mock-ci-integration-service.ts │ │ │ │ │ ├── mock-commit-change-service.ts │ │ │ │ │ ├── mock-commit-selection-service.ts │ │ │ │ │ ├── mock-context-menu-service.ts │ │ │ │ │ ├── mock-credential-service.ts │ │ │ │ │ ├── mock-d3-service.ts │ │ │ │ │ ├── mock-history-service.ts │ │ │ │ │ ├── mock-hotkeys-service.ts │ │ │ │ │ ├── mock-jira-service.ts │ │ │ │ │ ├── mock-layout-service.ts │ │ │ │ │ ├── mock-repo-service.ts │ │ │ │ │ └── mock-submodule-service.ts │ │ │ │ ├── open-repo-panel │ │ │ │ │ ├── open-repo-panel.component.html │ │ │ │ │ ├── open-repo-panel.component.scss │ │ │ │ │ ├── open-repo-panel.component.spec.ts │ │ │ │ │ └── open-repo-panel.component.ts │ │ │ │ ├── prompt │ │ │ │ │ ├── prompt-container.directive.ts │ │ │ │ │ ├── prompt.component.html │ │ │ │ │ ├── prompt.component.scss │ │ │ │ │ ├── prompt.component.spec.ts │ │ │ │ │ └── prompt.component.ts │ │ │ │ ├── prototypes │ │ │ │ │ ├── branch.ts │ │ │ │ │ ├── commit.ts │ │ │ │ │ └── file-detail.ts │ │ │ │ ├── services │ │ │ │ │ ├── appveyor-ci.service.spec.ts │ │ │ │ │ ├── appveyor-ci.service.ts │ │ │ │ │ ├── ci-integration.service.spec.ts │ │ │ │ │ ├── ci-integration.service.ts │ │ │ │ │ ├── commit-change.service.spec.ts │ │ │ │ │ ├── commit-change.service.ts │ │ │ │ │ ├── commit-selection.service.spec.ts │ │ │ │ │ ├── commit-selection.service.ts │ │ │ │ │ ├── credentials.service.spec.ts │ │ │ │ │ ├── credentials.service.ts │ │ │ │ │ ├── history.service.spec.ts │ │ │ │ │ ├── history.service.ts │ │ │ │ │ ├── layout.service.spec.ts │ │ │ │ │ ├── layout.service.ts │ │ │ │ │ ├── repo.service.spec.ts │ │ │ │ │ ├── repo.service.ts │ │ │ │ │ ├── submodules.service.spec.ts │ │ │ │ │ └── submodules.service.ts │ │ │ │ ├── ssh-password-prompt │ │ │ │ │ ├── ssh-password-prompt.component.html │ │ │ │ │ ├── ssh-password-prompt.component.scss │ │ │ │ │ ├── ssh-password-prompt.component.spec.ts │ │ │ │ │ └── ssh-password-prompt.component.ts │ │ │ │ ├── status-bar │ │ │ │ │ ├── status-bar.component.html │ │ │ │ │ ├── status-bar.component.scss │ │ │ │ │ ├── status-bar.component.spec.ts │ │ │ │ │ └── status-bar.component.ts │ │ │ │ ├── submodule-details-panel │ │ │ │ │ ├── submodule-details-panel.component.html │ │ │ │ │ ├── submodule-details-panel.component.scss │ │ │ │ │ ├── submodule-details-panel.component.spec.ts │ │ │ │ │ └── submodule-details-panel.component.ts │ │ │ │ ├── subway-station-annot │ │ │ │ │ ├── subway-station-annot.component.html │ │ │ │ │ ├── subway-station-annot.component.scss │ │ │ │ │ ├── subway-station-annot.component.spec.ts │ │ │ │ │ └── subway-station-annot.component.ts │ │ │ │ ├── subway-stations │ │ │ │ │ ├── subway-stations.component.html │ │ │ │ │ ├── subway-stations.component.scss │ │ │ │ │ ├── subway-stations.component.spec.ts │ │ │ │ │ └── subway-stations.component.ts │ │ │ │ ├── subway │ │ │ │ │ ├── subway.component.css │ │ │ │ │ ├── subway.component.html │ │ │ │ │ ├── subway.component.spec.ts │ │ │ │ │ └── subway.component.ts │ │ │ │ ├── tag-prompt │ │ │ │ │ ├── tag-prompt.component.html │ │ │ │ │ ├── tag-prompt.component.scss │ │ │ │ │ ├── tag-prompt.component.spec.ts │ │ │ │ │ └── tag-prompt.component.ts │ │ │ │ └── visuals │ │ │ │ │ ├── shared │ │ │ │ │ ├── link-visual │ │ │ │ │ │ ├── link-visual.component.html │ │ │ │ │ │ ├── link-visual.component.scss │ │ │ │ │ │ ├── link-visual.component.spec.ts │ │ │ │ │ │ └── link-visual.component.ts │ │ │ │ │ └── node-visual │ │ │ │ │ │ ├── node-visual.component.html │ │ │ │ │ │ ├── node-visual.component.scss │ │ │ │ │ │ ├── node-visual.component.spec.ts │ │ │ │ │ │ └── node-visual.component.ts │ │ │ │ │ └── subway │ │ │ │ │ └── subway-map-visual │ │ │ │ │ ├── subway-map-visual.component.html │ │ │ │ │ ├── subway-map-visual.component.scss │ │ │ │ │ ├── subway-map-visual.component.spec.ts │ │ │ │ │ └── subway-map-visual.component.ts │ │ │ ├── infrastructure │ │ │ │ ├── about-page │ │ │ │ │ ├── about-page.component.html │ │ │ │ │ ├── about-page.component.scss │ │ │ │ │ ├── about-page.component.spec.ts │ │ │ │ │ └── about-page.component.ts │ │ │ │ ├── cache.service.spec.ts │ │ │ │ ├── cache.service.ts │ │ │ │ ├── electron.service.spec.ts │ │ │ │ ├── electron.service.ts │ │ │ │ ├── icheck │ │ │ │ │ ├── icheck.component.html │ │ │ │ │ ├── icheck.component.scss │ │ │ │ │ ├── icheck.component.spec.ts │ │ │ │ │ └── icheck.component.ts │ │ │ │ ├── infrastructure.module.ts │ │ │ │ ├── loading-screen │ │ │ │ │ ├── loading-screen.component.css │ │ │ │ │ ├── loading-screen.component.html │ │ │ │ │ ├── loading-screen.component.spec.ts │ │ │ │ │ └── loading-screen.component.ts │ │ │ │ ├── loading-service.service.spec.ts │ │ │ │ ├── loading-service.service.ts │ │ │ │ ├── mocks │ │ │ │ │ ├── mock-electron-service.ts │ │ │ │ │ ├── mock-loading-service.ts │ │ │ │ │ ├── mock-prompt-injector-service.ts │ │ │ │ │ ├── mock-status-bar-service.ts │ │ │ │ │ └── mock-updater-service.ts │ │ │ │ ├── prompt-injector.service.spec.ts │ │ │ │ ├── prompt-injector.service.ts │ │ │ │ ├── prompt.ts │ │ │ │ ├── release-note │ │ │ │ │ ├── release-note.component.html │ │ │ │ │ ├── release-note.component.scss │ │ │ │ │ ├── release-note.component.spec.ts │ │ │ │ │ └── release-note.component.ts │ │ │ │ ├── spinner │ │ │ │ │ ├── spinner.component.html │ │ │ │ │ ├── spinner.component.scss │ │ │ │ │ ├── spinner.component.spec.ts │ │ │ │ │ └── spinner.component.ts │ │ │ │ ├── status-bar.service.spec.ts │ │ │ │ ├── status-bar.service.ts │ │ │ │ ├── updater.service.spec.ts │ │ │ │ └── updater.service.ts │ │ │ ├── jira │ │ │ │ ├── add-comment-prompt │ │ │ │ │ ├── add-comment-prompt.component.html │ │ │ │ │ ├── add-comment-prompt.component.scss │ │ │ │ │ ├── add-comment-prompt.component.spec.ts │ │ │ │ │ └── add-comment-prompt.component.ts │ │ │ │ ├── jira-detail │ │ │ │ │ ├── jira-detail.component.html │ │ │ │ │ ├── jira-detail.component.scss │ │ │ │ │ ├── jira-detail.component.spec.ts │ │ │ │ │ └── jira-detail.component.ts │ │ │ │ ├── jira-rich-text │ │ │ │ │ ├── jira-rich-text.component.html │ │ │ │ │ ├── jira-rich-text.component.scss │ │ │ │ │ ├── jira-rich-text.component.spec.ts │ │ │ │ │ └── jira-rich-text.component.ts │ │ │ │ ├── jira.module.ts │ │ │ │ ├── key-selector │ │ │ │ │ ├── key-selector.component.html │ │ │ │ │ ├── key-selector.component.scss │ │ │ │ │ ├── key-selector.component.spec.ts │ │ │ │ │ └── key-selector.component.ts │ │ │ │ ├── models │ │ │ │ │ ├── comment.ts │ │ │ │ │ ├── commit-message.ts │ │ │ │ │ ├── issue-type.ts │ │ │ │ │ ├── issue.ts │ │ │ │ │ ├── keyed-item.ts │ │ │ │ │ ├── priority.ts │ │ │ │ │ ├── profile.ts │ │ │ │ │ ├── resolution.ts │ │ │ │ │ ├── status.ts │ │ │ │ │ ├── subtask.ts │ │ │ │ │ └── transition.ts │ │ │ │ ├── profile-selector │ │ │ │ │ ├── profile-filter.ts │ │ │ │ │ ├── profile-selector.component.html │ │ │ │ │ ├── profile-selector.component.scss │ │ │ │ │ ├── profile-selector.component.spec.ts │ │ │ │ │ └── profile-selector.component.ts │ │ │ │ ├── resolution-control │ │ │ │ │ ├── resolution-control.component.html │ │ │ │ │ ├── resolution-control.component.scss │ │ │ │ │ ├── resolution-control.component.spec.ts │ │ │ │ │ └── resolution-control.component.ts │ │ │ │ ├── resolution-selector │ │ │ │ │ ├── resolution-selector.component.html │ │ │ │ │ ├── resolution-selector.component.scss │ │ │ │ │ ├── resolution-selector.component.spec.ts │ │ │ │ │ └── resolution-selector.component.ts │ │ │ │ ├── services │ │ │ │ │ ├── jira-integration.service.spec.ts │ │ │ │ │ ├── jira-integration.service.ts │ │ │ │ │ └── jira-issue-link-guard.ts │ │ │ │ ├── subtask-prompt │ │ │ │ │ ├── subtask-prompt.component.html │ │ │ │ │ ├── subtask-prompt.component.scss │ │ │ │ │ ├── subtask-prompt.component.spec.ts │ │ │ │ │ └── subtask-prompt.component.ts │ │ │ │ ├── title-editor │ │ │ │ │ ├── title-editor.component.html │ │ │ │ │ ├── title-editor.component.scss │ │ │ │ │ ├── title-editor.component.spec.ts │ │ │ │ │ └── title-editor.component.ts │ │ │ │ └── transition-control │ │ │ │ │ ├── transition-control.component.html │ │ │ │ │ ├── transition-control.component.scss │ │ │ │ │ ├── transition-control.component.spec.ts │ │ │ │ │ └── transition-control.component.ts │ │ │ └── settings │ │ │ │ ├── auth-settings │ │ │ │ ├── auth-settings.component.html │ │ │ │ ├── auth-settings.component.scss │ │ │ │ ├── auth-settings.component.spec.ts │ │ │ │ └── auth-settings.component.ts │ │ │ │ ├── ci-settings │ │ │ │ ├── ci-settings.component.html │ │ │ │ ├── ci-settings.component.scss │ │ │ │ ├── ci-settings.component.spec.ts │ │ │ │ └── ci-settings.component.ts │ │ │ │ ├── general-settings │ │ │ │ ├── general-settings.component.html │ │ │ │ ├── general-settings.component.scss │ │ │ │ ├── general-settings.component.spec.ts │ │ │ │ └── general-settings.component.ts │ │ │ │ ├── jira-settings │ │ │ │ ├── jira-settings.component.html │ │ │ │ ├── jira-settings.component.scss │ │ │ │ ├── jira-settings.component.spec.ts │ │ │ │ └── jira-settings.component.ts │ │ │ │ ├── mocks │ │ │ │ └── mock-settings-service.ts │ │ │ │ ├── profile-settings │ │ │ │ ├── profile-settings.component.html │ │ │ │ ├── profile-settings.component.scss │ │ │ │ ├── profile-settings.component.spec.ts │ │ │ │ └── profile-settings.component.ts │ │ │ │ ├── prototypes │ │ │ │ └── settings-component.ts │ │ │ │ ├── repo-profile │ │ │ │ ├── repo-profile.component.html │ │ │ │ ├── repo-profile.component.scss │ │ │ │ ├── repo-profile.component.spec.ts │ │ │ │ └── repo-profile.component.ts │ │ │ │ ├── services │ │ │ │ ├── settings.service.spec.ts │ │ │ │ └── settings.service.ts │ │ │ │ ├── settings-nav │ │ │ │ ├── settings-nav.component.html │ │ │ │ ├── settings-nav.component.scss │ │ │ │ ├── settings-nav.component.spec.ts │ │ │ │ └── settings-nav.component.ts │ │ │ │ ├── settings-page │ │ │ │ ├── settings-page.component.html │ │ │ │ ├── settings-page.component.scss │ │ │ │ ├── settings-page.component.spec.ts │ │ │ │ └── settings-page.component.ts │ │ │ │ ├── settings.module.ts │ │ │ │ └── updater │ │ │ │ ├── updater.component.html │ │ │ │ ├── updater.component.scss │ │ │ │ ├── updater.component.spec.ts │ │ │ │ └── updater.component.ts │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ ├── fonts │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── Lato-Black.ttf │ │ │ │ ├── Lato-BlackItalic.ttf │ │ │ │ ├── Lato-Bold.ttf │ │ │ │ ├── Lato-BoldItalic.ttf │ │ │ │ ├── Lato-Hairline.ttf │ │ │ │ ├── Lato-HairlineItalic.ttf │ │ │ │ ├── Lato-Italic.ttf │ │ │ │ ├── Lato-Light.ttf │ │ │ │ ├── Lato-LightItalic.ttf │ │ │ │ ├── Lato-Regular.ttf │ │ │ │ ├── NotoSans-Bold.ttf │ │ │ │ ├── NotoSans-BoldItalic.ttf │ │ │ │ ├── NotoSans-Italic.ttf │ │ │ │ ├── NotoSans-Regular.ttf │ │ │ │ ├── Roboto-Black.ttf │ │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ │ ├── Roboto-Italic.ttf │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-LightItalic.ttf │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ ├── Roboto-ThinItalic.ttf │ │ │ │ ├── icomoon.eot │ │ │ │ ├── icomoon.svg │ │ │ │ ├── icomoon.ttf │ │ │ │ └── icomoon.woff │ │ │ ├── icheck │ │ │ │ ├── blue.css │ │ │ │ ├── blue.png │ │ │ │ └── blue@2x.png │ │ │ ├── icons.css │ │ │ └── release-note │ │ │ │ ├── MG0.2.0-1.gif │ │ │ │ ├── MG0.2.0-2.gif │ │ │ │ ├── MG0.2.0-3.gif │ │ │ │ ├── MG0.2.0-4.gif │ │ │ │ ├── MG0.3.0-1.gif │ │ │ │ ├── MG0.3.0-2.gif │ │ │ │ ├── MG0.3.0-3.gif │ │ │ │ ├── MG0.4.0-1.gif │ │ │ │ ├── MG0.4.0-2.gif │ │ │ │ └── MG0.4.0-3.gif │ │ ├── bootstrap.min.css │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ ├── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ └── typings.d.ts │ ├── tsconfig.json │ ├── tslint.json │ └── yarn.lock ├── git │ ├── auto-fetch.js │ ├── external-file-view.js │ ├── file-watcher.js │ ├── repo-command-handler.js │ ├── repo-helpers.js │ ├── repo-history.js │ ├── repo.js │ └── submodules.js ├── infrastructure │ ├── auto-updater.js │ ├── cache.js │ ├── handler-helper.js │ ├── release-note.js │ ├── secure.js │ ├── settings.js │ └── shell.js ├── jira-integration │ └── jira.js ├── main.js └── visual │ ├── .DS_Store │ ├── Icon-1024.png │ ├── Icon-128.png │ ├── Icon-16.png │ ├── Icon-256.png │ ├── Icon-32.png │ ├── Icon-48.png │ ├── Icon-512.png │ ├── Icon-64.png │ ├── Icon.ai │ ├── Icon16.ai │ ├── Icon48.ai │ ├── MacOS │ ├── .DS_Store │ ├── metrogit.icns │ └── metrogit.iconset │ │ ├── .DS_Store │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── _variables.scss │ ├── appveyor.svg │ ├── bootstrap.min.css │ ├── icon.ico │ └── station-dot.svg ├── build.ps1 ├── build.sh ├── build ├── .DS_Store ├── icon.icns ├── icon.ico └── license_en.txt ├── dev-app-update.yml ├── install-dep.sh ├── misc ├── metrogit.gif ├── metrogit1.PNG ├── metrogit2.PNG ├── metrogit3.PNG ├── metrogit4.PNG └── metrogit5.PNG ├── package-lock.json ├── package.json ├── publish-linux.sh ├── publish.ps1 ├── test.sh └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Additional context** 14 | Add any other context or screenshots about the feature request here. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | runtime = electron 2 | target = 1.8.4 3 | target_arch = x64 4 | disturl = https://atom.io/download/electron -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | install: 5 | - "yarn install" 6 | - "cd app/frontend" 7 | - "yarn install" 8 | - "cd ../.." -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Electron: Main", 11 | "protocol": "inspector", 12 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 13 | "runtimeArgs": [ 14 | "--remote-debugging-port=9223", 15 | "." 16 | ], 17 | "windows": { 18 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 19 | } 20 | }, 21 | { 22 | "name": "Electron: Renderer", 23 | "type": "chrome", 24 | "request": "attach", 25 | "port": 9223, 26 | "webRoot": "${workspaceFolder}", 27 | "timeout": 30000 28 | } 29 | ], 30 | "compounds": [ 31 | { 32 | "name": "Electron: All", 33 | "configurations": [ 34 | "Electron: Main", 35 | "Electron: Renderer" 36 | ] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ming-Hung (Michael) Lu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/.DS_Store -------------------------------------------------------------------------------- /app/frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /app/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # e2e 39 | /e2e/*.js 40 | /e2e/*.map 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /app/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.3. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /app/frontend/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('frontend App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /app/frontend/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/frontend/karma-ci.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma'), 14 | 15 | ], 16 | client:{ 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageIstanbulReporter: { 20 | reports: [ 'html', 'lcovonly' ], 21 | fixWebpackSourcePaths: true 22 | }, 23 | angularCli: { 24 | environment: 'dev' 25 | }, 26 | customLaunchers: { 27 | ChromeHeadless: { 28 | base: 'Chrome', 29 | flags: [ 30 | '--headless', 31 | '--disable-gpu', 32 | '--no-sandbox', 33 | '--remote-debugging-port=9222', 34 | ] 35 | } 36 | }, 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['ChromeHeadless'], 42 | singleRun: true, 43 | reporters: ['progress', 'kjhtml'], 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /app/frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma'), 14 | 15 | ], 16 | client:{ 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageIstanbulReporter: { 20 | reports: [ 'html', 'lcovonly' ], 21 | fixWebpackSourcePaths: true 22 | }, 23 | angularCli: { 24 | environment: 'dev' 25 | }, 26 | reporters: ['progress', 'kjhtml'], 27 | port: 9876, 28 | colors: true, 29 | logLevel: config.LOG_INFO, 30 | autoWatch: true, 31 | browsers: ['Chrome'], 32 | singleRun: false 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /app/frontend/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 50000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /app/frontend/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | #parent-container{ 2 | overflow: hidden; 3 | min-height: 100vh; 4 | } -------------------------------------------------------------------------------- /app/frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/action-toolbar/action-toolbar.component.scss: -------------------------------------------------------------------------------- 1 | $item-height: 55px; 2 | $badge-size: 20px; 3 | 4 | :host{ 5 | width: 100%; 6 | } 7 | #action-button-container{ 8 | border-bottom: solid 1px #222; 9 | width: 100%; 10 | min-height: $item-height; 11 | cursor: pointer; 12 | .action-button{ 13 | width: 80px; 14 | height: $item-height; 15 | padding: 5px 10px; 16 | font-size: 35px; 17 | text-align: center; 18 | position: relative; 19 | app-spinner{ 20 | position: absolute; 21 | top: 10px; 22 | left: 20px; 23 | } 24 | &:hover{ 25 | background: rgba(0, 0, 0, 0.2); 26 | color: var(--blue); 27 | } 28 | .badge{ 29 | position: absolute; 30 | border-radius: 50%; 31 | background: rgb(5, 142, 217); 32 | width: $badge-size; 33 | height: $badge-size; 34 | top: 8px; 35 | right: 13px; 36 | font-size: 12px; 37 | padding: 0.25em 0; 38 | line-height: 14px; 39 | color: #FFF !important; 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/branch-item/branch-item.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | 5 | 6 | 7 | {{item.display}} 8 | 9 | 10 |
11 | 12 | 13 | Delete Branch 14 | 15 | 16 | 17 | 18 | Delete Tag 19 | 20 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/branch-item/branch-item.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | width: 100%; 3 | } 4 | 5 | .branch-folder, 6 | .branch { 7 | width: 100%; 8 | padding-top: 2px; 9 | padding-bottom: 2px; 10 | height: 30px; 11 | overflow: hidden; 12 | text-overflow: ellipsis; 13 | white-space: nowrap; 14 | position: relative; 15 | cursor: pointer; 16 | .color-marker { 17 | position: absolute; 18 | height: 100%; 19 | width: 5px; 20 | left: 0; 21 | opacity: 0; 22 | } 23 | } 24 | 25 | .branch:hover .color-marker { 26 | opacity: 1; 27 | } 28 | 29 | .branch-folder { 30 | &.toggled { 31 | height: auto; 32 | } 33 | .expand { 34 | margin-top: 5px; 35 | margin-right: 5px; 36 | float: right; 37 | } 38 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/branch-list/branch-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/branch-list/branch-list.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | width: 100%; 3 | height: 100%; 4 | 5 | } 6 | 7 | .branch-list-container{ 8 | width: 100%; 9 | height: 100%; 10 | padding-left: 10px; 11 | 12 | background: rgba(0,0,0,0.2); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/ci-console-output/ci-console-output.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | 8 |
Loading output...
9 |
10 |
11 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/ci-console-output/ci-console-output.component.scss: -------------------------------------------------------------------------------- 1 | .console-output-container { 2 | width: 100%; 3 | max-height: 310px; 4 | overflow: hidden; 5 | .output { 6 | font-family: 'Consolas'; 7 | background: rgba(0, 0, 0, 1); 8 | max-height: 300px; 9 | overflow-wrap: break-word; 10 | overflow-y: auto; 11 | } 12 | .loading-container{ 13 | width: 100%; 14 | height: 300px; 15 | } 16 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/ci-console-output/ci-console-output.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CiConsoleOutputComponent } from './ci-console-output.component'; 4 | import { InfrastructureModule } from '../../infrastructure/infrastructure.module'; 5 | 6 | describe('CiConsoleOutputComponent', () => { 7 | let component: CiConsoleOutputComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ CiConsoleOutputComponent ], 13 | imports: [ 14 | InfrastructureModule 15 | ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(CiConsoleOutputComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/ci-console-output/ci-console-output.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { SafeHtml, DomSanitizer } from '@angular/platform-browser'; 3 | 4 | @Component({ 5 | selector: 'app-ci-console-output', 6 | templateUrl: './ci-console-output.component.html', 7 | styleUrls: ['./ci-console-output.component.scss'] 8 | }) 9 | export class CiConsoleOutputComponent implements OnInit { 10 | 11 | @Input() loading = false; 12 | @Input() set text(txt: string) { 13 | let temp = txt; 14 | temp = temp.replace(/\n/g, '
'); 15 | this.display = this.sanitizer.bypassSecurityTrustHtml(temp); 16 | } 17 | private display: SafeHtml; 18 | constructor( 19 | private sanitizer: DomSanitizer 20 | ) { } 21 | 22 | ngOnInit() { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/commit-detail-ci/commit-detail-ci.component.scss: -------------------------------------------------------------------------------- 1 | .commit-detail-info-container { 2 | height: 100%; 3 | } 4 | 5 | .full-width { 6 | width: 100%; 7 | } 8 | 9 | .overall-container { 10 | .overall-ci { 11 | width: 50px; 12 | height: 50px; 13 | border-radius: 50%; 14 | .feather { 15 | width: 42px; 16 | height: 42px; 17 | margin-top: 4px; 18 | margin-left: 4px; 19 | } 20 | } 21 | h5 { 22 | line-height: 50px; 23 | } 24 | } 25 | 26 | .bg-gray { 27 | background: var(--gray) !important; 28 | } 29 | 30 | .selectable { 31 | user-select: initial; 32 | } 33 | 34 | .external-link { 35 | cursor: pointer; 36 | &:hover { 37 | color: var(--blue); 38 | } 39 | } 40 | 41 | .ci-status-container { 42 | overflow: hidden; 43 | .status-header { 44 | cursor: pointer; 45 | .logo-container { 46 | height: 30px; 47 | } 48 | h5 { 49 | margin: 0; 50 | line-height: 30px; 51 | } 52 | background: rgba(0, 0, 0, 0.2); 53 | } 54 | .status-content { 55 | background: rgba(0, 0, 0, 0.2); 56 | max-height: 0; 57 | } 58 | &.toggled .status-content { 59 | 60 | max-height: 400px; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/commit-file-list/commit-file-list.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | width: 100%; 3 | } 4 | 5 | .file-details-container { 6 | min-height: 0; 7 | height: 100%; 8 | } 9 | 10 | .modified-file-list { 11 | background: rgba(0, 0, 0, 0.2); 12 | overflow-y: auto; 13 | overflow-x: hidden; 14 | min-height: 0; 15 | user-select: initial; 16 | .modified-file-entry { 17 | white-space: nowrap; 18 | user-select: initial; 19 | cursor: pointer; 20 | position: relative; 21 | .btn-sm { 22 | height: 24px; 23 | font-size: 12px; 24 | padding: 2px; 25 | } 26 | &:hover { 27 | background: rgba(0, 0, 0, 0.2); 28 | .action-button { 29 | display: block; 30 | } 31 | } 32 | .action-button { 33 | display: none; 34 | position: absolute; 35 | right: 3px; 36 | top: 3px; 37 | line-height: 20px; 38 | } 39 | } 40 | } 41 | 42 | .action-button { 43 | cursor: pointer; 44 | font-size: 12px; 45 | background: var(--gray); 46 | border-radius: 3px; 47 | padding: 3px; 48 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/commit-file-list/commit-file-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommitFileListComponent } from './commit-file-list.component'; 4 | import { FileListFilter } from './file-list-filter'; 5 | 6 | describe('CommitFileListComponent', () => { 7 | let component: CommitFileListComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ CommitFileListComponent, FileListFilter ] 13 | }) 14 | .compileComponents(); 15 | })); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(CommitFileListComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/commit-file-list/file-list-filter.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'fileListFilter', 5 | pure: false 6 | }) 7 | export class FileListFilter implements PipeTransform { 8 | transform(items: any[], filter: FileListFilterMask): any { 9 | if (!items || !filter) { 10 | return items; 11 | } 12 | return items.filter(item => { 13 | if (item.isAdded && filter.added) { 14 | return true; 15 | } else if (item.isDeleted && filter.deleted) { 16 | return true; 17 | } else if (item.isModified && filter.modified) { 18 | return true; 19 | } else if (item.isRenamed && filter.renamed) { 20 | return true; 21 | } else { 22 | return false; 23 | } 24 | }); 25 | } 26 | } 27 | 28 | export class FileListFilterMask { 29 | added: boolean; 30 | deleted: boolean; 31 | modified: boolean; 32 | renamed: boolean; 33 | constructor(add, del, modif, rena) { 34 | this.added = add; 35 | this.deleted = del; 36 | this.modified = modif; 37 | this.renamed = rena; 38 | } 39 | showAll(): boolean { 40 | return this.added && this.deleted && this.modified && this.renamed; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/committer-card/committer-card.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{authorIcn}} 4 |
5 |
6 |

{{_author}}

7 | {{_email}} 8 | at 9 | {{timeStr}} 10 |
11 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/committer-card/committer-card.component.scss: -------------------------------------------------------------------------------- 1 | .committer-info-container { 2 | width: 100%; 3 | } 4 | .committer-badge { 5 | flex-shrink: 0; 6 | width: 70px; 7 | height: 70px; 8 | border-radius: 50%; 9 | line-height: 70px; 10 | text-align: center; 11 | font-size: 36px; 12 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4); 13 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/committer-card/committer-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommitterCardComponent } from './committer-card.component'; 4 | import { D3Service } from '../d3/d3.service'; 5 | import { MockD3 } from '../mocks/mock-d3-service'; 6 | 7 | describe('CommitterCardComponent', () => { 8 | let component: CommitterCardComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [CommitterCardComponent], 14 | providers: [ 15 | { provide: D3Service, useClass: MockD3 } 16 | ] 17 | }) 18 | .compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(CommitterCardComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/committer-card/committer-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; 3 | import { D3Service } from '../d3/d3.service'; 4 | import * as moment from 'moment'; 5 | 6 | @Component({ 7 | selector: 'app-committer-card', 8 | templateUrl: './committer-card.component.html', 9 | styleUrls: ['./committer-card.component.scss'] 10 | }) 11 | export class CommitterCardComponent implements OnInit { 12 | 13 | @Input() set author(a) { 14 | this.getCommitter(a); 15 | this._author = a; 16 | } 17 | @Input() set email(em) { 18 | this.getBadgeColor(em); 19 | this._email = em; 20 | } 21 | @Input() set time(t) { 22 | this.getDateTime(t); 23 | } 24 | private authorIcn = ""; 25 | private badgeColor: SafeStyle; 26 | private timeStr = ""; 27 | private _email = ""; 28 | private _author = ""; 29 | constructor( 30 | private sanitize: DomSanitizer, 31 | private d3: D3Service, 32 | ) { } 33 | 34 | ngOnInit() { 35 | } 36 | getBadgeColor(email) { 37 | this.badgeColor = this.sanitize.bypassSecurityTrustStyle(`${this.d3.getColorByAuthor(email)}`); 38 | } 39 | getDateTime(time) { 40 | this.timeStr = moment(time).format('MM/DD/YYYY hh:mm a'); 41 | } 42 | getCommitter(comitter) { 43 | this.authorIcn = this.d3.getAuthor(comitter); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/create-branch-prompt/create-branch-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Please enter new branch name
3 | 4 |
5 | 6 |
7 | Create Branch 8 | Cancel 9 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/create-branch-prompt/create-branch-prompt.component.scss: -------------------------------------------------------------------------------- 1 | .login-form{ 2 | width: 300px; 3 | height: 100%; 4 | 5 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/create-branch-prompt/create-branch-prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CreateBranchPromptComponent } from './create-branch-prompt.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | 6 | describe('CreateBranchPromptComponent', () => { 7 | let component: CreateBranchPromptComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ CreateBranchPromptComponent ], 13 | imports: [ 14 | FormsModule 15 | ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(CreateBranchPromptComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/create-branch-prompt/create-branch-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter } from '@angular/core'; 2 | import { Prompt } from '../../infrastructure/prompt'; 3 | 4 | @Component({ 5 | selector: 'app-create-branch-prompt', 6 | templateUrl: './create-branch-prompt.component.html', 7 | styleUrls: ['./create-branch-prompt.component.scss'] 8 | }) 9 | export class CreateBranchPromptComponent implements OnInit, Prompt { 10 | 11 | toClose = new EventEmitter(); 12 | branchName = ""; 13 | onEnter = new EventEmitter(); 14 | constructor() { } 15 | 16 | ngOnInit() { 17 | } 18 | close() { 19 | this.toClose.emit(); 20 | } 21 | enter() { 22 | this.onEnter.emit(this.branchName); 23 | this.close(); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/d3/d3.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { D3Service } from './d3.service'; 4 | import { MockCIIntegration } from '../mocks/mock-ci-integration-service'; 5 | import { CiIntegrationService } from '../services/ci-integration.service'; 6 | 7 | describe('D3Service', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [ 11 | D3Service, 12 | { provide: CiIntegrationService, useClass: MockCIIntegration } 13 | ] 14 | }); 15 | }); 16 | 17 | it('should be created', inject([D3Service], (service: D3Service) => { 18 | expect(service).toBeTruthy(); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/d3/models/color.ts: -------------------------------------------------------------------------------- 1 | export class Color { 2 | r: number; 3 | g: number; 4 | b: number; 5 | static parseHex(inString: string): Color { 6 | let color = new Color(0, 0, 0); 7 | color.setHex(inString); 8 | return color; 9 | } 10 | 11 | constructor(r, g, b) { 12 | this.r = r; 13 | this.g = g; 14 | this.b = b; 15 | } 16 | setHex(value: string): void { 17 | let parsed = value.split('#'); 18 | if (parsed.length > 1 && parsed[1].length === 6) { 19 | let segment1 = parsed[1].substring(0, 2); 20 | let segment2 = parsed[1].substring(2, 4); 21 | let segment3 = parsed[1].substring(4, 6); 22 | this.r = parseInt(segment1, 16); 23 | this.g = parseInt(segment2, 16); 24 | this.b = parseInt(segment3, 16); 25 | } 26 | } 27 | get stringValue() { 28 | return `rgba(${this.r},${this.g},${this.b},1)`; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/d3/models/link.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node'; 2 | import { Color } from './color'; 3 | 4 | export class Link { 5 | 6 | color?: Color; 7 | merge = false; 8 | source: Node; 9 | target: Node; 10 | 11 | constructor(source, target) { 12 | this.source = source; 13 | this.target = target; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/d3/models/node.ts: -------------------------------------------------------------------------------- 1 | import { Commit } from "../../prototypes/commit"; 2 | import { Color } from "./color"; 3 | 4 | export class Node { 5 | 6 | static height = 35; 7 | 8 | commit?: Commit; 9 | x?: number; 10 | y?: number; 11 | color?: Color; 12 | secondColor?: Color; 13 | id: string; 14 | processed = false; 15 | x_order = 0; 16 | 17 | constructor(id) { 18 | this.id = id; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/enter-login-prompt/enter-login-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Please enter your login information
3 | 4 |
5 | 6 |
7 |
8 | 9 |
10 | Enter 11 | Cancel 12 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/enter-login-prompt/enter-login-prompt.component.scss: -------------------------------------------------------------------------------- 1 | .login-form{ 2 | width: 300px; 3 | height: 100%; 4 | 5 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/enter-login-prompt/enter-login-prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EnterLoginPromptComponent } from './enter-login-prompt.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | 6 | describe('EnterLoginPromptComponent', () => { 7 | let component: EnterLoginPromptComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ EnterLoginPromptComponent ], 13 | imports: [ 14 | FormsModule 15 | ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(EnterLoginPromptComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/enter-login-prompt/enter-login-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter } from '@angular/core'; 2 | import { Prompt } from '../../infrastructure/prompt'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-enter-login-prompt', 7 | templateUrl: './enter-login-prompt.component.html', 8 | styleUrls: ['./enter-login-prompt.component.scss'] 9 | }) 10 | export class EnterLoginPromptComponent implements OnInit, Prompt { 11 | 12 | toClose = new EventEmitter(); 13 | username = ""; 14 | password = ""; 15 | onEnter = new EventEmitter<{username: string, password: string}>(); 16 | constructor() { } 17 | 18 | ngOnInit() { 19 | } 20 | close() { 21 | this.toClose.emit(); 22 | } 23 | enter() { 24 | this.onEnter.emit({username: this.username, password: this.password}); 25 | this.close(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/external-file-viewer/external-file-viewer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{path}} @ {{getTitle()}} 5 | {{fileDetail.summary.added}} + 6 | {{fileDetail.summary.removed}} - 7 |

8 |
9 |
10 |
11 | 12 |
{{oldPath}} has no {{fileDetail.commit === 'workdir' ? 'unstaged' : 'staged'}} changes
13 |
14 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/external-file-viewer/external-file-viewer.component.scss: -------------------------------------------------------------------------------- 1 | .file-panel{ 2 | background: var(--gray-dark); 3 | width: 100vw; 4 | height: 100%; 5 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/file-counts/file-counts.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{modified}} 4 | 5 | {{newCount}} 6 | 7 | {{deleted}} 8 | 9 | {{renamed}} 10 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/file-counts/file-counts.component.scss: -------------------------------------------------------------------------------- 1 | .commit-summary{ 2 | cursor: pointer; 3 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/file-counts/file-counts.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FileCountsComponent } from './file-counts.component'; 4 | import { LayoutService } from '../services/layout.service'; 5 | import { MockLayout } from '../mocks/mock-layout-service'; 6 | import { NgbModule } from '../../../../node_modules/@ng-bootstrap/ng-bootstrap'; 7 | 8 | describe('FileCountsComponent', () => { 9 | let component: FileCountsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ FileCountsComponent ], 15 | imports: [ 16 | NgbModule.forRoot(), 17 | ], 18 | providers: [ 19 | {provide: LayoutService, useClass: MockLayout} 20 | ] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(FileCountsComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/file-counts/file-counts.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { LayoutService } from '../services/layout.service'; 3 | 4 | @Component({ 5 | selector: 'app-file-counts', 6 | templateUrl: './file-counts.component.html', 7 | styleUrls: ['./file-counts.component.scss'] 8 | }) 9 | export class FileCountsComponent implements OnInit { 10 | 11 | @Input() modified = 0; 12 | @Input() newCount = 0; 13 | @Input() deleted = 0; 14 | @Input() renamed = 0; 15 | @Output() modifiedClicked = new EventEmitter(); 16 | @Output() newClicked = new EventEmitter(); 17 | @Output() deletedClicked = new EventEmitter(); 18 | @Output() renamedClicked = new EventEmitter(); 19 | private tooltip = true; 20 | constructor( 21 | private layout: LayoutService 22 | ) { 23 | layout.tooltipChanged.subscribe(tp => { 24 | this.tooltip = tp; 25 | }); 26 | this.tooltip = layout.tooltipEnabled; 27 | } 28 | 29 | ngOnInit() { 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/force-push-prompt/force-push-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Your local is behind! Do you want to force push?
3 | Force Push 4 | Cancel 5 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/force-push-prompt/force-push-prompt.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/core/force-push-prompt/force-push-prompt.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/core/force-push-prompt/force-push-prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ForcePushPromptComponent } from './force-push-prompt.component'; 4 | 5 | describe('ForcePushPromptComponent', () => { 6 | let component: ForcePushPromptComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ForcePushPromptComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ForcePushPromptComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/force-push-prompt/force-push-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter } from '@angular/core'; 2 | import { Prompt } from '../../infrastructure/prompt'; 3 | 4 | @Component({ 5 | selector: 'app-force-push-prompt', 6 | templateUrl: './force-push-prompt.component.html', 7 | styleUrls: ['./force-push-prompt.component.scss'] 8 | }) 9 | export class ForcePushPromptComponent implements OnInit, Prompt { 10 | 11 | toClose = new EventEmitter(); 12 | onResult = new EventEmitter(); 13 | constructor() { } 14 | 15 | ngOnInit() { 16 | } 17 | 18 | enter() { 19 | this.toClose.emit(); 20 | this.onResult.emit(true); 21 | } 22 | close() { 23 | this.toClose.emit(); 24 | this.onResult.emit(false); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/git-view/git-view.component.css: -------------------------------------------------------------------------------- 1 | :host{ 2 | width: 100%; 3 | } 4 | #outer-container{ 5 | min-height: 100vh; 6 | padding: 0; 7 | position: relative; 8 | } 9 | #main-container{ 10 | flex: 1; 11 | height: 100%; 12 | min-width: 100vh; 13 | border-bottom: solid 1px #222; 14 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); 15 | z-index: 10; 16 | position: relative; 17 | } 18 | #footer-container{ 19 | height: 30px; 20 | min-width: 100vh; 21 | background-color: #34495e; 22 | z-index: 9; 23 | } 24 | #inner-container{ 25 | width: 100%; 26 | } 27 | 28 | .open-repo-prompt{ 29 | position: absolute; 30 | left: 35px; 31 | top: 5px; 32 | } 33 | .hidden{ 34 | opacity: 0; 35 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/git-view/git-view.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 |
7 |
8 | Click the folder icon to open a repository
9 |
10 | 11 | 12 |
13 | 16 |
17 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/git-view/git-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { RepoService } from '../services/repo.service'; 3 | import { Commit } from '../prototypes/commit'; 4 | import { LoadingService } from '../../infrastructure/loading-service.service'; 5 | 6 | @Component({ 7 | selector: 'app-git-view', 8 | templateUrl: './git-view.component.html', 9 | styleUrls: ['./git-view.component.css'] 10 | }) 11 | export class GitViewComponent implements OnInit { 12 | 13 | private repoName: string = null; 14 | private commits: Commit[] = []; 15 | constructor( 16 | private repoService: RepoService, 17 | private loading: LoadingService 18 | ) { 19 | } 20 | 21 | ngOnInit() { 22 | let that = this; 23 | this.repoService.repoChange.subscribe(newRepoName => { 24 | this.loading.enableLoading(); 25 | setTimeout(() => { 26 | that.repoName = newRepoName; 27 | that.loading.disableLoading(); 28 | }); 29 | }); 30 | this.repoService.commitsChange.subscribe(commits => { 31 | this.commits = commits; 32 | }); 33 | if (this.repoService.hasRepository) { 34 | this.loading.enableLoading(); 35 | setTimeout(() => { 36 | that.repoName = that.repoService.repoName; 37 | that.commits = that.repoService.getCommitsWithWIP(); 38 | that.loading.disableLoading(); 39 | }); 40 | } 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/map-separator/map-separator.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{sep.display}} 4 |
5 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/map-separator/map-separator.component.scss: -------------------------------------------------------------------------------- 1 | .separator-container{ 2 | width:100%; 3 | top: 7px; 4 | position: absolute; 5 | z-index: -1; 6 | .separator{ 7 | opacity: 0.6; 8 | text-align: left; 9 | border-top: solid 1px var(--gray); 10 | color: var(--gray); 11 | padding-left: 35px; 12 | position: absolute; 13 | width: 100%; 14 | line-height: 10px; 15 | small{ 16 | font-size: 12px; 17 | } 18 | } 19 | } 20 | .hidden{ 21 | display: none; 22 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/map-separator/map-separator.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MapSeparatorComponent } from './map-separator.component'; 4 | 5 | describe('MapSeparatorComponent', () => { 6 | let component: MapSeparatorComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MapSeparatorComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MapSeparatorComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-appveyor-ci-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockAppVeyor { 4 | @Output() buildsUpdated = new EventEmitter(); 5 | @Output() enabledChanged = new EventEmitter(); 6 | @Output() logRetrieved = new EventEmitter<{ build: string, output: string }>(); 7 | 8 | buildResults; 9 | enabled; 10 | constructor( 11 | ) { 12 | 13 | } 14 | 15 | init() { 16 | 17 | } 18 | 19 | openAppveyor(commit) { 20 | } 21 | 22 | getBuildLog(commit) { 23 | } 24 | 25 | rebuildAppveyor(commit) { 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-ci-integration-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockCIIntegration { 4 | @Output() buildsUpdated = new EventEmitter(); 5 | @Output() enabledChanged = new EventEmitter(); 6 | buildResults: any; 7 | 8 | enabled = false; 9 | constructor( 10 | ) { 11 | } 12 | 13 | init() { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-commit-change-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "@angular/core"; 2 | 3 | export class MockCommitChange { 4 | 5 | @Output() messageChange = new EventEmitter(); 6 | @Output() detailChange = new EventEmitter(); 7 | @Output() stashed = new EventEmitter(); 8 | @Output() popped = new EventEmitter(); 9 | @Output() commitingChange = new EventEmitter(); 10 | defaultKey = ""; 11 | set newCommitMessage(msg) { 12 | } 13 | get newCommitMessage() { 14 | return ""; 15 | } 16 | set newCommitDetail(msg) { 17 | } 18 | get newCommitDetail() { 19 | return null; 20 | } 21 | constructor( 22 | ) { 23 | } 24 | 25 | init() { 26 | 27 | } 28 | stage(paths): void { 29 | } 30 | stageLines(path, lines) { 31 | } 32 | unstage(paths): void { 33 | } 34 | unstageLines(path, lines) { 35 | } 36 | commit(paths): void { 37 | } 38 | commitStaged(): void { 39 | } 40 | stash(): void { 41 | } 42 | pop(index = -1): void { 43 | } 44 | apply(index = -1): void { 45 | } 46 | deleteStash(index): void { 47 | } 48 | discardAll(): void { 49 | } 50 | tryCommit(): void { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-commit-selection-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "@angular/core"; 2 | import { CommitDetail, WIPCommit } from "../prototypes/commit"; 3 | import { FileDetail } from "../prototypes/file-detail"; 4 | 5 | export class MockCommitSelection { 6 | @Output() selectionChange = new EventEmitter(); 7 | @Output() selectingChange = new EventEmitter(); 8 | @Output() selectedFileChange = new EventEmitter(); 9 | @Output() fileDetailChanged = new EventEmitter(); 10 | @Output() gettingFileDetail = new EventEmitter(); 11 | selectedCommit: CommitDetail | WIPCommit; 12 | constructor( 13 | ) { 14 | } 15 | 16 | selectFileDetail(file, sha = null, fullFile = false) { 17 | } 18 | subscribeLiveFileUpdate(file, commit, fullFile) { 19 | } 20 | select(commit) { 21 | } 22 | openExternalFileView(file, sha = null) { 23 | } 24 | reset(commit, mode): void { 25 | } 26 | createTag(commit): void { 27 | } 28 | deleteTag(name): void { 29 | } 30 | deleteBranch(name): void { 31 | } 32 | deleteRemoteBranch(name): void { 33 | } 34 | unsubscribeFileUpdate(): void { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-context-menu-service.ts: -------------------------------------------------------------------------------- 1 | export class MockContextMenuService { 2 | show = { 3 | next(a: any) { 4 | 5 | }, 6 | subscribe() { 7 | 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-credential-service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockCredential { 4 | username = ""; 5 | password = ""; 6 | email = ""; 7 | name = ""; 8 | credentialChange = new EventEmitter<{ username: string, password: string }>(); 9 | constructor( 10 | ) { 11 | } 12 | 13 | init() { 14 | 15 | } 16 | 17 | promptUserUpdateCredential() { 18 | } 19 | 20 | promptUserEnterSSHPassword() { 21 | } 22 | 23 | notifyCredentialChange() { 24 | } 25 | 26 | updateCredentials(username, password) { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-d3-service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "@angular/core"; 2 | 3 | export class MockD3 { 4 | mapChange = new EventEmitter(); 5 | 6 | scrollTo(commit) { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-history-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockHistory { 4 | @Output() historyChange = new EventEmitter(); 5 | repos = []; 6 | constructor( 7 | ) { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-hotkeys-service.ts: -------------------------------------------------------------------------------- 1 | export class MockHotkeys { 2 | add(hotkey: any, a, desc) { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-layout-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "@angular/core"; 2 | 3 | export class MockLayout { 4 | 5 | isLocalShown = true; 6 | isRemoteShown = true; 7 | isTagsShown = true; 8 | isDetailPanelOpen = false; 9 | isSubmoduleShown = true; 10 | set tooltipEnabled(tp) { } 11 | get tooltipEnabled() { return true; } 12 | 13 | set isNavToggled(val) { } 14 | get isNavToggled() { return true; } 15 | set isFilePanelOpen(val) { } 16 | get isFilePanelOpen() { return true; } 17 | 18 | @Output() filePanelChanged = new EventEmitter(); 19 | @Output() navPanelChanged = new EventEmitter(); 20 | @Output() tooltipChanged = new EventEmitter(); 21 | constructor( 22 | ) { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/mocks/mock-submodule-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "@angular/core"; 2 | 3 | export class MockSubmodule { 4 | @Output() submoduleChanged = new EventEmitter(); 5 | @Output() submoduleSelected = new EventEmitter(); 6 | @Output() submoduleDetailChanged = new EventEmitter(); 7 | submodules; 8 | selectedSubmodule = ""; 9 | submoduleDetails; 10 | constructor( 11 | ) { 12 | } 13 | selectSubmodule(name) { 14 | } 15 | getSubmoduleDetails(name) { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/open-repo-panel/open-repo-panel.component.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #FFF; 2 | 3 | :host{ 4 | height: 100%; 5 | position: absolute; 6 | top: 0; 7 | background-color: var(--gray-dark); 8 | } 9 | 10 | .panel-container{ 11 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.8); 12 | width: 0px; 13 | color: $primary-color; 14 | height: 100%; 15 | position: relative; 16 | overflow: hidden; 17 | &.toggled{ 18 | width: 600px; 19 | } 20 | 21 | .close-btn-container{ 22 | position: absolute; 23 | top: 10px; 24 | right: 10px; 25 | .close-btn{ 26 | cursor: pointer; 27 | } 28 | i{ 29 | font-size: 30px; 30 | } 31 | } 32 | } 33 | .recent-item{ 34 | width: 100%; 35 | cursor: pointer; 36 | border-top: 1px solid #222; 37 | h5{ 38 | margin: 0; 39 | } 40 | &:hover{ 41 | background: rgba(0, 0, 0, 0.2); 42 | .action-button-container { 43 | opacity: 1; 44 | } 45 | } 46 | .action-button-container{ 47 | opacity: 0; 48 | align-items: center; 49 | } 50 | 51 | } 52 | .full-width{ 53 | width: 100%; 54 | } 55 | .flex-no-shrink{ 56 | flex-shrink: 0; 57 | } 58 | .history-container{ 59 | min-height: 0; 60 | overflow: auto; 61 | } 62 | .history{ 63 | height: 100%; 64 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/prompt/prompt-container.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ViewContainerRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | // tslint:disable-next-line:directive-selector 5 | selector: 'app-prompt-container' 6 | }) 7 | export class PromptContainerDirective { 8 | 9 | constructor( 10 | public viewContainerRef: ViewContainerRef 11 | ) { } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/prompt/prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/prompt/prompt.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | position: absolute; 3 | width: 100%; 4 | left: 0; 5 | top: -55px; 6 | z-index: 15; 7 | height: 55px; 8 | } 9 | 10 | :host.toggled{ 11 | top: 0; 12 | } 13 | 14 | .prompt-container{ 15 | width: 100%; 16 | height: 100%; 17 | background: var(--dark); 18 | border-radius: 0 0 5px 5px; 19 | border: solid 1px #222; 20 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.8); 21 | text-align: center; 22 | position: relative; 23 | .prompt-inner{ 24 | margin: 0 auto; 25 | height: 100%; 26 | } 27 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/prompt/prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PromptComponent } from './prompt.component'; 4 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 5 | import { PromptInjectorService } from '../../infrastructure/prompt-injector.service'; 6 | import { MockPromptInjector } from '../../infrastructure/mocks/mock-prompt-injector-service'; 7 | import { InfrastructureModule } from '../../infrastructure/infrastructure.module'; 8 | import { PromptContainerDirective } from './prompt-container.directive'; 9 | 10 | describe('PromptComponent', () => { 11 | let component: PromptComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [ PromptComponent, PromptContainerDirective ], 17 | providers: [ 18 | {provide: PromptInjectorService, useClass: MockPromptInjector} 19 | ], 20 | }) 21 | .compileComponents(); 22 | })); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(PromptComponent); 26 | component = fixture.componentInstance; 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', () => { 31 | expect(component).toBeTruthy(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/prompt/prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, HostBinding, ViewChild, ComponentFactory, AfterViewInit } from '@angular/core'; 2 | import { PromptContainerDirective } from './prompt-container.directive'; 3 | import { PromptInjectorService } from '../../infrastructure/prompt-injector.service'; 4 | 5 | @Component({ 6 | selector: 'app-prompt', 7 | templateUrl: './prompt.component.html', 8 | styleUrls: ['./prompt.component.scss'] 9 | }) 10 | export class PromptComponent implements OnInit, AfterViewInit { 11 | 12 | @HostBinding('class') cls = "smooth"; 13 | @HostBinding('class.toggled') toggled = false; 14 | @ViewChild(PromptContainerDirective) appPromptContainer: PromptContainerDirective; 15 | 16 | constructor( 17 | private prompt: PromptInjectorService 18 | ) { 19 | } 20 | 21 | ngOnInit() { 22 | } 23 | 24 | ngAfterViewInit() { 25 | this.prompt.init(this.appPromptContainer.viewContainerRef); 26 | this.prompt.componentChange.subscribe(prompt => { 27 | prompt.toClose.subscribe(() => { 28 | this.hide(); 29 | }); 30 | this.show(); 31 | }); 32 | } 33 | 34 | show() { 35 | this.toggled = true; 36 | } 37 | 38 | hide() { 39 | this.toggled = false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/prototypes/branch.ts: -------------------------------------------------------------------------------- 1 | export interface Branch { 2 | name: string; 3 | fullName: string; 4 | shorthand: string; 5 | target: string; 6 | } 7 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/prototypes/file-detail.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface FileDetail { 3 | commit: string; 4 | paths: string[]; 5 | path: string; 6 | hunks: [ 7 | { 8 | lines: [{ 9 | op: string; 10 | content: string; 11 | oldLineno: number; 12 | newLineno: number; 13 | }] 14 | } 15 | ]; 16 | summary: { 17 | added: number, 18 | removed: number, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/appveyor-ci.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { AppveyorCiService } from './appveyor-ci.service'; 4 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 5 | import { ElectronService } from '../../infrastructure/electron.service'; 6 | import { SimpleNotificationsModule } from '../../../../node_modules/angular2-notifications'; 7 | import { MockLoading } from '../../infrastructure/mocks/mock-loading-service'; 8 | import { LoadingService } from '../../infrastructure/loading-service.service'; 9 | 10 | describe('AppveyorCiService', () => { 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | SimpleNotificationsModule.forRoot(), 15 | ], 16 | providers: [ 17 | AppveyorCiService, 18 | { provide: ElectronService, useClass: MockElectron }, 19 | { provide: LoadingService, useClass: MockLoading }, 20 | ] 21 | }); 22 | }); 23 | 24 | it('should be created', inject([AppveyorCiService], (service: AppveyorCiService) => { 25 | expect(service).toBeTruthy(); 26 | })); 27 | }); 28 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/ci-integration.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { CiIntegrationService } from './ci-integration.service'; 4 | import { ElectronService } from '../../infrastructure/electron.service'; 5 | import { StatusBarService } from '../../infrastructure/status-bar.service'; 6 | import { RepoService } from './repo.service'; 7 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 8 | import { MockRepo } from '../mocks/mock-repo-service'; 9 | import { MockStatusBar } from '../../infrastructure/mocks/mock-status-bar-service'; 10 | 11 | describe('CiIntegrationService', () => { 12 | beforeEach(() => { 13 | TestBed.configureTestingModule({ 14 | providers: [ 15 | CiIntegrationService, 16 | {provide: ElectronService, useClass: MockElectron}, 17 | {provide: StatusBarService, useClass: MockStatusBar}, 18 | {provide: RepoService, useClass: MockRepo} 19 | ] 20 | }); 21 | }); 22 | 23 | it('should be created', inject([CiIntegrationService], (service: CiIntegrationService) => { 24 | expect(service).toBeTruthy(); 25 | })); 26 | }); 27 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/credentials.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { CredentialsService } from './credentials.service'; 4 | import { ElectronService } from '../../infrastructure/electron.service'; 5 | import { PromptInjectorService } from '../../infrastructure/prompt-injector.service'; 6 | import { MockPromptInjector } from '../../infrastructure/mocks/mock-prompt-injector-service'; 7 | import { SimpleNotificationsModule } from '../../../../node_modules/angular2-notifications'; 8 | import { RouterTestingModule } from '../../../../node_modules/@angular/router/testing'; 9 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 10 | 11 | describe('CredentialsService', () => { 12 | beforeEach(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | SimpleNotificationsModule.forRoot(), 16 | RouterTestingModule, 17 | ], 18 | providers: [ 19 | CredentialsService, 20 | {provide: ElectronService, useClass: MockElectron}, 21 | {provide: PromptInjectorService, useClass: MockPromptInjector}, 22 | ] 23 | }); 24 | }); 25 | 26 | it('should be created', inject([CredentialsService], (service: CredentialsService) => { 27 | expect(service).toBeTruthy(); 28 | })); 29 | }); 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/history.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { HistoryService } from './history.service'; 4 | import { ElectronService } from '../../infrastructure/electron.service'; 5 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 6 | 7 | describe('HistoryService', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [ 11 | HistoryService, 12 | {provide: ElectronService, useClass: MockElectron} 13 | ] 14 | }); 15 | }); 16 | 17 | it('should be created', inject([HistoryService], (service: HistoryService) => { 18 | expect(service).toBeTruthy(); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/history.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, EventEmitter, Output } from '@angular/core'; 2 | import { ElectronService } from '../../infrastructure/electron.service'; 3 | 4 | @Injectable() 5 | export class HistoryService { 6 | 7 | @Output() historyChange = new EventEmitter(); 8 | repos = []; 9 | constructor( 10 | private electron: ElectronService 11 | ) { 12 | electron.onCD('Repo-HistoryChanged', (event, arg) => { 13 | this.repos = arg.history; 14 | this.historyChange.emit(this.repos); 15 | }); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/layout.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { LayoutService } from './layout.service'; 4 | import { ElectronService } from '../../infrastructure/electron.service'; 5 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 6 | import { HotkeysService } from '../../../../node_modules/angular2-hotkeys'; 7 | import { MockHotkeys } from '../mocks/mock-hotkeys-service'; 8 | 9 | describe('LayoutService', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | providers: [ 13 | LayoutService, 14 | {provide: ElectronService, useClass: MockElectron}, 15 | {provide: HotkeysService, useClass: MockHotkeys} 16 | ] 17 | }); 18 | }); 19 | 20 | it('should be created', inject([LayoutService], (service: LayoutService) => { 21 | expect(service).toBeTruthy(); 22 | })); 23 | }); 24 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/submodules.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { SubmodulesService } from './submodules.service'; 4 | import { ElectronService } from '../../infrastructure/electron.service'; 5 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 6 | 7 | describe('SubmodulesService', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [ 11 | SubmodulesService, 12 | {provide: ElectronService, useClass: MockElectron} 13 | ] 14 | }); 15 | }); 16 | 17 | it('should be created', inject([SubmodulesService], (service: SubmodulesService) => { 18 | expect(service).toBeTruthy(); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/services/submodules.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Output, EventEmitter } from '@angular/core'; 2 | import { ElectronService } from '../../infrastructure/electron.service'; 3 | 4 | @Injectable() 5 | export class SubmodulesService { 6 | 7 | @Output() submoduleChanged = new EventEmitter(); 8 | @Output() submoduleSelected = new EventEmitter(); 9 | @Output() submoduleDetailChanged = new EventEmitter(); 10 | submodules; 11 | selectedSubmodule = ""; 12 | submoduleDetails; 13 | constructor( 14 | private electron: ElectronService 15 | ) { 16 | this.electron.onCD('Repo-SubmoduleNamesRetrieved', (event, arg) => { 17 | this.submodules = arg.submodules; 18 | this.submoduleChanged.emit(this.submodules); 19 | }); 20 | this.electron.onCD('Repo-SubmoduleDetailsRetrieved', (event, arg) => { 21 | this.submoduleDetails = arg.result; 22 | this.submoduleDetailChanged.emit(this.submoduleDetails); 23 | }); 24 | } 25 | 26 | selectSubmodule(name) { 27 | this.selectedSubmodule = name; 28 | this.submoduleSelected.emit(name); 29 | } 30 | 31 | getSubmoduleDetails(name) { 32 | this.electron.ipcRenderer.send('Repo-GetSubmoduleDetails', {name: name}); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/ssh-password-prompt/ssh-password-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Please enter the password for your SSH key
3 |
4 | 5 |
6 | Enter 7 | Cancel 8 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/ssh-password-prompt/ssh-password-prompt.component.scss: -------------------------------------------------------------------------------- 1 | .login-form{ 2 | width: 300px; 3 | height: 100%; 4 | 5 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/ssh-password-prompt/ssh-password-prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SshPasswordPromptComponent } from './ssh-password-prompt.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | 6 | describe('SshPasswordPromptComponent', () => { 7 | let component: SshPasswordPromptComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | FormsModule, 14 | ], 15 | declarations: [ SshPasswordPromptComponent ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(SshPasswordPromptComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/ssh-password-prompt/ssh-password-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter } from '@angular/core'; 2 | import { Prompt } from '../../infrastructure/prompt'; 3 | 4 | @Component({ 5 | selector: 'app-ssh-password-prompt', 6 | templateUrl: './ssh-password-prompt.component.html', 7 | styleUrls: ['./ssh-password-prompt.component.scss'] 8 | }) 9 | export class SshPasswordPromptComponent implements OnInit, Prompt { 10 | 11 | toClose = new EventEmitter(); 12 | password = ""; 13 | onEnter = new EventEmitter<{password: string}>(); 14 | constructor() { } 15 | 16 | ngOnInit() { 17 | } 18 | close() { 19 | this.toClose.emit(); 20 | } 21 | enter() { 22 | this.onEnter.emit({password: this.password}); 23 | this.close(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/status-bar/status-bar.component.html: -------------------------------------------------------------------------------- 1 |
2 |
5 |
6 | 8 |
9 |
10 | {{message}} 11 |
12 |
13 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/status-bar/status-bar.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .status-bar-container { 7 | width: 100%; 8 | height: 100%; 9 | position: relative; 10 | font-size: 16px; 11 | padding: 3px 10px; 12 | overflow: hidden; 13 | } 14 | 15 | .status-icon-container{ 16 | width: 16px; 17 | transform-origin: 50%; 18 | text-align: center; 19 | } 20 | .status-message-container{ 21 | width: 100%; 22 | } 23 | 24 | .message-container{ 25 | position: absolute; 26 | top: 50px; 27 | width: 700px; 28 | transition: all 0.4s ease; 29 | } 30 | 31 | .show{ 32 | top: 4px; 33 | } 34 | 35 | .loading-spinner { 36 | animation: rotating 0.9s linear infinite; 37 | } 38 | 39 | @keyframes rotating 40 | { 41 | from { 42 | -webkit-transform: rotate(0deg); 43 | -o-transform: rotate(0deg); 44 | transform: rotate(0deg); 45 | } 46 | to { 47 | -webkit-transform: rotate(360deg); 48 | -o-transform: rotate(360deg); 49 | transform: rotate(360deg); 50 | } 51 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/status-bar/status-bar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { StatusBarComponent } from './status-bar.component'; 4 | import { MockStatusBar } from '../../infrastructure/mocks/mock-status-bar-service'; 5 | import { StatusBarService } from '../../infrastructure/status-bar.service'; 6 | 7 | describe('StatusBarComponent', () => { 8 | let component: StatusBarComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ StatusBarComponent ], 14 | providers: [ 15 | {provide: StatusBarService, useClass: MockStatusBar} 16 | ] 17 | }) 18 | .compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(StatusBarComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/status-bar/status-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { StatusBarService } from '../../infrastructure/status-bar.service'; 3 | 4 | @Component({ 5 | selector: 'app-status-bar', 6 | templateUrl: './status-bar.component.html', 7 | styleUrls: ['./status-bar.component.scss'] 8 | }) 9 | export class StatusBarComponent implements OnInit { 10 | 11 | private loading = false; 12 | private message = ""; 13 | private type = ""; 14 | private show = false; 15 | constructor( 16 | private sbService: StatusBarService 17 | ) { 18 | sbService.statusChange.subscribe(status => { 19 | if (status.show) { 20 | this.loading = status.loading; 21 | this.message = status.message; 22 | this.type = status.type; 23 | } 24 | this.show = status.show; 25 | }); 26 | } 27 | 28 | ngOnInit() { 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/submodule-details-panel/submodule-details-panel.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |
8 |

Submodule: {{_name}}

9 |
Head @ {{details.hid.substring(0, 6)}}
10 | 11 |
12 |
13 |
{{details.message}}
14 |
15 |
16 | {{details.detail}} 17 |
18 |
19 |
20 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/submodule-details-panel/submodule-details-panel.component.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #FFF; 2 | 3 | :host{ 4 | height: 100%; 5 | position: absolute; 6 | top: 0; 7 | background-color: var(--gray-dark); 8 | } 9 | 10 | .panel-container{ 11 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.8); 12 | width: 0px; 13 | color: $primary-color; 14 | height: 100%; 15 | position: relative; 16 | overflow: hidden; 17 | &.toggled{ 18 | width: 600px; 19 | } 20 | 21 | .close-btn-container{ 22 | position: absolute; 23 | top: 10px; 24 | right: 10px; 25 | .close-btn{ 26 | cursor: pointer; 27 | } 28 | i{ 29 | font-size: 30px; 30 | } 31 | } 32 | .message-title-container { 33 | background: rgba(0, 0, 0, 0.2); 34 | border-bottom: solid 2px #222; 35 | h5 { 36 | margin: 0; 37 | } 38 | user-select: initial; 39 | } 40 | .message-container { 41 | height: 100%; 42 | } 43 | .message-content-container { 44 | background: rgba(0, 0, 0, 0.2); 45 | overflow-y: auto; 46 | user-select: initial; 47 | height: 100%; 48 | } 49 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/submodule-details-panel/submodule-details-panel.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SubmoduleDetailsPanelComponent } from './submodule-details-panel.component'; 4 | import { SubmodulesService } from '../services/submodules.service'; 5 | import { MockSubmodule } from '../mocks/mock-submodule-service'; 6 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 7 | 8 | describe('SubmoduleDetailsPanelComponent', () => { 9 | let component: SubmoduleDetailsPanelComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ SubmoduleDetailsPanelComponent ], 15 | providers: [ 16 | {provide: SubmodulesService, useClass: MockSubmodule} 17 | ], 18 | schemas: [NO_ERRORS_SCHEMA] 19 | }) 20 | .compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(SubmoduleDetailsPanelComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/submodule-details-panel/submodule-details-panel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { SubmodulesService } from '../services/submodules.service'; 3 | 4 | @Component({ 5 | selector: 'app-submodule-details-panel', 6 | templateUrl: './submodule-details-panel.component.html', 7 | styleUrls: ['./submodule-details-panel.component.scss'] 8 | }) 9 | export class SubmoduleDetailsPanelComponent implements OnInit { 10 | 11 | @Input() toggled = false; 12 | @Input() set submoduleName(n) { 13 | this._name = n; 14 | this.submodules.getSubmoduleDetails(n); 15 | } 16 | private details; 17 | private _name = ""; 18 | constructor( 19 | private submodules: SubmodulesService 20 | ) { 21 | submodules.submoduleDetailChanged.subscribe(result => { 22 | this.details = result; 23 | }); 24 | } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/subway/subway.component.css: -------------------------------------------------------------------------------- 1 | :host{ 2 | width: 100%; 3 | height: 100%; 4 | position: relative; 5 | overflow-y: auto; 6 | overflow-x: hidden; 7 | } 8 | 9 | .hidden{ 10 | display: none; 11 | } 12 | 13 | .subway-outer{ 14 | height: 100%; 15 | position: absolute; 16 | width: 100%; 17 | } 18 | 19 | .subway-container{ 20 | padding-left: 50px; 21 | border-bottom: solid 1px #222; 22 | box-shadow: 0 0 6px rgba(0, 0, 0, 0.4); 23 | position: relative; 24 | height: auto; 25 | } 26 | 27 | .eoh-container{ 28 | color: #6c757d; 29 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/subway/subway.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 | 8 |
9 |

End of History

10 |
11 |
12 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/subway/subway.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { Commit } from '../prototypes/commit'; 3 | import { Node } from '../d3/models/node'; 4 | import { Link } from '../d3/models/link'; 5 | import { Color } from '../d3/models/color'; 6 | import { RepoService } from '../services/repo.service'; 7 | 8 | @Component({ 9 | selector: 'app-subway', 10 | templateUrl: './subway.component.html', 11 | styleUrls: ['./subway.component.css'] 12 | }) 13 | export class SubwayComponent implements OnInit { 14 | 15 | @Input() commits: Commit[]; 16 | hasRepo = false; 17 | constructor(private repo: RepoService) { 18 | this.hasRepo = repo.hasRepository; 19 | if (repo.hasRepository) { 20 | this.commits = repo.commits; 21 | } 22 | repo.repoChange.subscribe(name => { 23 | if (!name) { 24 | this.hasRepo = false; 25 | } else { 26 | this.hasRepo = true; 27 | } 28 | }); 29 | } 30 | 31 | ngOnInit() { 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/tag-prompt/tag-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Enter tag name @ {{sha.substring(0, 6)}}
3 |
4 | 5 |
6 | Create 7 | Cancel 8 |
-------------------------------------------------------------------------------- /app/frontend/src/app/core/tag-prompt/tag-prompt.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/core/tag-prompt/tag-prompt.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/core/tag-prompt/tag-prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TagPromptComponent } from './tag-prompt.component'; 4 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 5 | 6 | describe('TagPromptComponent', () => { 7 | let component: TagPromptComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ TagPromptComponent ], 13 | schemas: [NO_ERRORS_SCHEMA] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(TagPromptComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/tag-prompt/tag-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter } from '@angular/core'; 2 | import { Prompt } from '../../infrastructure/prompt'; 3 | 4 | @Component({ 5 | selector: 'app-tag-prompt', 6 | templateUrl: './tag-prompt.component.html', 7 | styleUrls: ['./tag-prompt.component.scss'] 8 | }) 9 | export class TagPromptComponent implements OnInit, Prompt { 10 | 11 | toClose = new EventEmitter(); 12 | toCreate = new EventEmitter<{sha: string, name: string}>(); 13 | sha = ""; 14 | private tagName = ""; 15 | constructor() { } 16 | 17 | ngOnInit() { 18 | } 19 | 20 | enter() { 21 | this.toCreate.emit({sha: this.sha, name: this.tagName}); 22 | this.toClose.emit(); 23 | } 24 | close() { 25 | this.toClose.emit(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/visuals/shared/link-visual/link-visual.component.scss: -------------------------------------------------------------------------------- 1 | .line { 2 | fill: none; 3 | stroke-width: 4; 4 | stroke-miterlimit: 10; 5 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/visuals/shared/link-visual/link-visual.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, ChangeDetectorRef, AfterViewInit } from '@angular/core'; 2 | import { Link } from '../../../d3/models/link'; 3 | 4 | @Component({ 5 | // tslint:disable-next-line:component-selector 6 | selector: '[linkVisual]', 7 | templateUrl: './link-visual.component.html', 8 | styleUrls: ['./link-visual.component.scss'] 9 | }) 10 | export class LinkVisualComponent implements OnInit, AfterViewInit { 11 | 12 | // tslint:disable-next-line:no-input-rename 13 | @Input('linkVisual') link: Link; 14 | linkDirection = -1; 15 | mergeDirection = 1; 16 | constructor( 17 | private cdr: ChangeDetectorRef 18 | ) { 19 | 20 | } 21 | 22 | ngAfterViewInit() { 23 | this.cdr.detach(); 24 | } 25 | ngOnInit(): void { 26 | if (this.link.target.y > this.link.source.y && this.link.target.x > this.link.source.x) { 27 | this.linkDirection = 1; 28 | } 29 | if (!this.link.merge) { 30 | this.mergeDirection = -1; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/visuals/shared/node-visual/node-visual.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | cursor: pointer; 3 | } 4 | 5 | :host:hover{ 6 | .highlight{ 7 | opacity: 0.3; 8 | } 9 | } 10 | 11 | .highlight.selected{ 12 | opacity: 0.3; 13 | } 14 | 15 | .dot { 16 | fill: #FFFFFF; 17 | stroke-width: 4; 18 | stroke-miterlimit: 10; 19 | } 20 | 21 | .highlight{ 22 | opacity: 0.1; 23 | border-right: solid 1px transparent; 24 | } -------------------------------------------------------------------------------- /app/frontend/src/app/core/visuals/subway/subway-map-visual/subway-map-visual.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/frontend/src/app/core/visuals/subway/subway-map-visual/subway-map-visual.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | // width: 403px; 3 | flex-shrink: 0; 4 | } 5 | .dot { 6 | fill: #FFFFFF; 7 | stroke-width: 5; 8 | stroke-miterlimit: 10; 9 | } -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/about-page/about-page.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | About

4 |
5 |

MetroGit 0.4.0

6 |
Repository: https://github.com/Yamazaki93/MetroGit
7 |
Love this app? I will greatly appreciate it if you can Buy Me A Tea !
8 |
License: MIT © Ming-Hung (Michael) Lu
9 |
10 |
-------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/about-page/about-page.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/infrastructure/about-page/about-page.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/about-page/about-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AboutPageComponent } from './about-page.component'; 4 | import { ElectronService } from '../electron.service'; 5 | import { MockElectron } from '../mocks/mock-electron-service'; 6 | 7 | describe('AboutPageComponent', () => { 8 | let component: AboutPageComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ AboutPageComponent ], 14 | providers: [ 15 | {provide: ElectronService, useClass: MockElectron} 16 | ] 17 | }) 18 | .compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(AboutPageComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/about-page/about-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ElectronService } from '../electron.service'; 3 | 4 | @Component({ 5 | selector: 'app-about-page', 6 | templateUrl: './about-page.component.html', 7 | styleUrls: ['./about-page.component.scss'] 8 | }) 9 | export class AboutPageComponent implements OnInit { 10 | 11 | constructor( 12 | private electron: ElectronService 13 | ) { 14 | } 15 | 16 | ngOnInit() { 17 | } 18 | 19 | goToRepo() { 20 | this.electron.ipcRenderer.send('Shell-Open', {url: 'https://github.com/Yamazaki93/MetroGit'}); 21 | } 22 | goToBMC() { 23 | this.electron.ipcRenderer.send('Shell-Open', {url: 'https://www.buymeacoffee.com/mjCsGWDTS'}); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/cache.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { CacheService } from './cache.service'; 4 | import { ElectronService } from './electron.service'; 5 | import { MockElectron } from './mocks/mock-electron-service'; 6 | import { StatusBarService } from './status-bar.service'; 7 | import { MockStatusBar } from './mocks/mock-status-bar-service'; 8 | 9 | describe('CacheService', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | providers: [ 13 | CacheService, 14 | {provide: ElectronService, useClass: MockElectron}, 15 | {provide: StatusBarService, useClass: MockStatusBar} 16 | ], 17 | }); 18 | }); 19 | 20 | it('should be created', inject([CacheService], (service: CacheService) => { 21 | expect(service).toBeTruthy(); 22 | })); 23 | }); 24 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/cache.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ElectronService } from './electron.service'; 3 | import { StatusBarService } from './status-bar.service'; 4 | 5 | @Injectable() 6 | export class CacheService { 7 | 8 | constructor( 9 | private electron: ElectronService, 10 | private statusBar: StatusBarService 11 | ) { 12 | this.electron.onCD('Cache-AutoCleanBegin', (event, arg) => { 13 | this.statusBar.enableLoading('Starting auto cache cleanup'); 14 | }); 15 | this.electron.onCD('Cache-AutoCleanSuccess', (event, arg) => { 16 | this.statusBar.flash('success', "Auto cache cleanup successful"); 17 | }); 18 | } 19 | init() { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/electron.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { ElectronService } from './electron.service'; 4 | 5 | // take care of declare var electron; 6 | (window as any).electron = undefined; 7 | 8 | describe('ElectronService', () => { 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | providers: [ElectronService] 12 | }); 13 | }); 14 | 15 | it('should be created', inject([ElectronService], (service: ElectronService) => { 16 | expect(service).toBeTruthy(); 17 | })); 18 | }); 19 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/electron.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, ChangeDetectorRef, NgZone } from '@angular/core'; 2 | declare var electron: any; 3 | 4 | 5 | @Injectable() 6 | export class ElectronService { 7 | private initialized = false; 8 | ipcRenderer = null; 9 | constructor( 10 | private zone: NgZone 11 | ) { 12 | if (electron) { 13 | this.ipcRenderer = electron.ipcRenderer; 14 | this.initialized = true; 15 | } else { 16 | console.warn('Electron not available'); 17 | } 18 | } 19 | 20 | // safe subscribe method for angular change detection 21 | onCD(event: string, handler: Function) { 22 | if (this.available) { 23 | this.ipcRenderer.on(event, (ev, arg) => { 24 | this.zone.run(() => { 25 | handler(ev, arg); 26 | }); 27 | }); 28 | } 29 | } 30 | on(event: string, handler: Function) { 31 | if (this.available) { 32 | this.ipcRenderer.on(event, (ev, arg) => { 33 | handler(ev, arg); 34 | }); 35 | } 36 | } 37 | 38 | openUrlExternal(url: string) { 39 | if (this.available) { 40 | this.ipcRenderer.send('Shell-Open', {url: url}); 41 | } 42 | } 43 | 44 | get available(): boolean { 45 | return this.initialized; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/icheck/icheck.component.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/icheck/icheck.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | font-family: "Consolas", "Microsoft YaHei", Arial, arial, sans-serif; 3 | overflow: hidden; 4 | } 5 | 6 | :host > div > div { 7 | width: 24px; 8 | height: 24px; 9 | display: inline-block; 10 | vertical-align: middle; 11 | background: url('https://cdn.rawgit.com/fronteed/icheck/1.x/skins/square/blue.png') no-repeat left; 12 | background-position: 0 0; 13 | cursor: pointer; 14 | } 15 | 16 | :host > div > div:hover { 17 | background-position: -24px 0; 18 | } 19 | 20 | :host > div > div.disabled { 21 | background-position: -72px 0; 22 | cursor: default; 23 | } 24 | 25 | :host > div > div.checked { 26 | background-position: -48px 0; 27 | } 28 | 29 | :host > div > div.checked.disabled { 30 | background-position: -96px 0; 31 | } 32 | 33 | :host .label { 34 | display: inline-block; 35 | vertical-align: middle; 36 | } -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/icheck/icheck.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { IcheckComponent } from './icheck.component'; 4 | 5 | describe('IcheckComponent', () => { 6 | let component: IcheckComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ IcheckComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(IcheckComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/icheck/icheck.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icheck', 5 | templateUrl: './icheck.component.html', 6 | styleUrls: ['./icheck.component.scss'] 7 | }) 8 | export class IcheckComponent implements OnInit { 9 | 10 | @Input() disabled = false; 11 | @Output() checkedChange = new EventEmitter(); 12 | 13 | @Input() 14 | set checked(value) { 15 | this.checkedValue = value; 16 | } 17 | get checked() { 18 | return this.checkedValue; 19 | } 20 | private checkedValue = false; 21 | constructor() { } 22 | 23 | ngOnInit() { 24 | } 25 | 26 | onClick() { 27 | if (!this.disabled) { 28 | this.checked = !this.checked; 29 | this.checkedChange.emit(this.checkedValue); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/infrastructure.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LoadingScreenComponent } from './loading-screen/loading-screen.component'; 4 | import { SpinnerComponent } from './spinner/spinner.component'; 5 | import { LoadingService } from './loading-service.service'; 6 | import { ElectronService } from './electron.service'; 7 | import { IcheckComponent } from './icheck/icheck.component'; 8 | import { PromptInjectorService } from './prompt-injector.service'; 9 | import { StatusBarService } from './status-bar.service'; 10 | import { UpdaterService } from './updater.service'; 11 | import { ReleaseNoteComponent } from './release-note/release-note.component'; 12 | import { AboutPageComponent } from './about-page/about-page.component'; 13 | import { CacheService } from './cache.service'; 14 | 15 | @NgModule({ 16 | imports: [ 17 | CommonModule 18 | ], 19 | exports: [LoadingScreenComponent, SpinnerComponent, IcheckComponent], 20 | declarations: [LoadingScreenComponent, SpinnerComponent, IcheckComponent, ReleaseNoteComponent, AboutPageComponent], 21 | providers: [LoadingService, ElectronService, PromptInjectorService, StatusBarService, UpdaterService, CacheService] 22 | }) 23 | export class InfrastructureModule { } 24 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/loading-screen/loading-screen.component.css: -------------------------------------------------------------------------------- 1 | :host{ 2 | width: 100vw; 3 | height: 100vh; 4 | background: rgba(0, 0, 0, 0.8); 5 | position: absolute; 6 | top: 0; 7 | z-index: 9999; 8 | } 9 | .loading-container{ 10 | height: 100vh; 11 | } 12 | .loading-text-container{ 13 | width: 100vw; 14 | text-align: center 15 | } -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/loading-screen/loading-screen.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
{{message}}
5 |
6 |
-------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/loading-screen/loading-screen.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, HostBinding, ViewChild } from '@angular/core'; 2 | import { LoadingService } from '../loading-service.service'; 3 | import { SpinnerComponent } from '../spinner/spinner.component'; 4 | 5 | @Component({ 6 | selector: 'app-loading-screen', 7 | templateUrl: './loading-screen.component.html', 8 | styleUrls: ['./loading-screen.component.css'] 9 | }) 10 | export class LoadingScreenComponent implements OnInit { 11 | 12 | message = ""; 13 | @HostBinding('style.display') style = 'none'; 14 | @ViewChild('spinner') spinner: SpinnerComponent; 15 | set enabled(newStatus: boolean) { 16 | this._enabled = newStatus; 17 | if (this._enabled) { 18 | this.style = 'block'; 19 | } else { 20 | this.style = 'none'; 21 | } 22 | this.spinner.enabled = this._enabled; 23 | } 24 | 25 | private _enabled = false; 26 | constructor( 27 | private loadingService: LoadingService 28 | ) { 29 | loadingService.change.subscribe(isBusy => { 30 | this.enabled = isBusy; 31 | }); 32 | loadingService.messageChange.subscribe(message => { 33 | this.message = message; 34 | }); 35 | } 36 | 37 | ngOnInit() { 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/mocks/mock-electron-service.ts: -------------------------------------------------------------------------------- 1 | export class MockElectron { 2 | private handlers: { 3 | msg: string 4 | cb: Function, 5 | }[]; 6 | private messageSent: string[] = []; 7 | 8 | constructor() { 9 | this.handlers = []; 10 | } 11 | 12 | ipcRenderer = { 13 | send: (event, handler) => { 14 | this.messageSent.push(event); 15 | } 16 | }; 17 | 18 | onCD(event: string, handler: Function) { 19 | this.handlers.push({ 20 | msg: event, 21 | cb: handler, 22 | }); 23 | } 24 | on(event: string, handler: Function) { 25 | this.handlers.push({ 26 | msg: event, 27 | cb: handler, 28 | }); 29 | } 30 | receiveEvent(event: string, arg: any) { 31 | this.handlers.forEach(h => { 32 | if (h.msg === event) { 33 | h.cb(undefined, arg); 34 | } 35 | }); 36 | } 37 | messageWasSent(event: string) { 38 | return this.messageSent.indexOf(event) !== -1; 39 | } 40 | openUrlExternal(url: string) { 41 | } 42 | 43 | get available(): boolean { 44 | return true; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/mocks/mock-loading-service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, Output } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockLoading { 4 | get isBusy(): boolean { 5 | return true; 6 | } 7 | @Output() 8 | messageChange: EventEmitter = new EventEmitter(); 9 | change: EventEmitter = new EventEmitter(); 10 | 11 | constructor( 12 | ) { } 13 | updateMessage(message) { 14 | } 15 | enableLoading(message = "Loading...") { 16 | } 17 | disableLoading() { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/mocks/mock-prompt-injector-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter, Type } from "../../../../node_modules/@angular/core"; 2 | import { Prompt } from "../prompt"; 3 | 4 | export class MockPromptInjector { 5 | 6 | @Output() componentChange = new EventEmitter(); 7 | constructor( 8 | ) { } 9 | 10 | init() { 11 | } 12 | 13 | injectComponent(component: Type): T { 14 | return undefined; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/mocks/mock-status-bar-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockStatusBar { 4 | @Output() statusChange = new EventEmitter(); 5 | constructor() { } 6 | 7 | enableLoading(message) { 8 | } 9 | disableLoading() { 10 | } 11 | flash(type, message) { 12 | } 13 | private hide() { 14 | } 15 | } 16 | 17 | interface Status { 18 | loading: boolean; 19 | type: string; 20 | message: string; 21 | show: boolean; 22 | } 23 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/mocks/mock-updater-service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockUpdater { 4 | isUpdateAvailable = false; 5 | updateVersion = ""; 6 | updateChecking: EventEmitter = new EventEmitter(); 7 | updateAvailableChange: EventEmitter = new EventEmitter(); 8 | constructor( 9 | ) { 10 | 11 | } 12 | checkUpdate() { 13 | } 14 | installUpdate() { 15 | } 16 | init() { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/prompt-injector.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { PromptInjectorService } from './prompt-injector.service'; 4 | 5 | describe('PromptInjectorService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [PromptInjectorService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([PromptInjectorService], (service: PromptInjectorService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/prompt-injector.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, ComponentFactoryResolver, ViewContainerRef, Type, Output, EventEmitter } from '@angular/core'; 2 | import { Prompt } from './prompt'; 3 | 4 | @Injectable() 5 | export class PromptInjectorService { 6 | 7 | @Output() componentChange = new EventEmitter(); 8 | private vcf: ViewContainerRef; 9 | constructor( 10 | private cfr: ComponentFactoryResolver 11 | ) { } 12 | 13 | init(vcf: ViewContainerRef) { 14 | this.vcf = vcf; 15 | } 16 | 17 | injectComponent(component: Type): T { 18 | this.vcf.clear(); 19 | let cf = this.cfr.resolveComponentFactory(component); 20 | let componentRef = this.vcf.createComponent(cf); 21 | this.componentChange.emit(componentRef.instance); 22 | return componentRef.instance; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/prompt.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "@angular/core"; 2 | 3 | export interface Prompt { 4 | toClose: EventEmitter<{}>; 5 | } 6 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/release-note/release-note.component.scss: -------------------------------------------------------------------------------- 1 | h3{ 2 | color: var(--blue); 3 | } 4 | 5 | img{ 6 | width: 50vw; 7 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.8); 8 | margin: 10px 0; 9 | } -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/release-note/release-note.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ReleaseNoteComponent } from './release-note.component'; 4 | 5 | describe('ReleaseNoteComponent', () => { 6 | let component: ReleaseNoteComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ReleaseNoteComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ReleaseNoteComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/release-note/release-note.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-release-note', 5 | templateUrl: './release-note.component.html', 6 | styleUrls: ['./release-note.component.scss'] 7 | }) 8 | export class ReleaseNoteComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/spinner/spinner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SpinnerComponent } from './spinner.component'; 4 | 5 | describe('SpinnerComponent', () => { 6 | let component: SpinnerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SpinnerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SpinnerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/spinner/spinner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, HostBinding } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-spinner', 5 | templateUrl: './spinner.component.html', 6 | styleUrls: ['./spinner.component.scss'], 7 | }) 8 | export class SpinnerComponent implements OnInit { 9 | @Input() enabled = false; 10 | @Input() size = '40'; 11 | @HostBinding('class') class = 'd-flex justify-content-center align-items-center'; 12 | constructor() { } 13 | 14 | ngOnInit() { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/status-bar.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { StatusBarService } from './status-bar.service'; 4 | 5 | describe('StatusBarService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [StatusBarService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([StatusBarService], (service: StatusBarService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/status-bar.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class StatusBarService { 5 | 6 | @Output() statusChange = new EventEmitter(); 7 | private existingTimeout = null; 8 | constructor() { } 9 | 10 | enableLoading(message) { 11 | this.statusChange.emit({loading: true, type: 'loading', message: message, show: true}); 12 | } 13 | disableLoading() { 14 | this.hide(); 15 | } 16 | flash(type, message) { 17 | this.statusChange.emit({loading: false, type: type, message: message, show: true}); 18 | let that = this; 19 | if (this.existingTimeout) { 20 | clearTimeout(this.existingTimeout); 21 | } 22 | let timeout = 7000; 23 | if (type === 'warning' || type === 'danger') { 24 | timeout = 30 * 1000; 25 | } 26 | this.existingTimeout = setTimeout(() => { 27 | that.hide(); 28 | }, timeout); 29 | } 30 | private hide() { 31 | this.statusChange.emit({loading: false, type: null, message: null, show: false}); 32 | } 33 | } 34 | 35 | interface Status { 36 | loading: boolean; 37 | type: string; 38 | message: string; 39 | show: boolean; 40 | } 41 | -------------------------------------------------------------------------------- /app/frontend/src/app/infrastructure/updater.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { UpdaterService } from './updater.service'; 4 | import { ElectronService } from './electron.service'; 5 | import { MockElectron } from './mocks/mock-electron-service'; 6 | import { StatusBarService } from './status-bar.service'; 7 | import { MockStatusBar } from './mocks/mock-status-bar-service'; 8 | import { SimpleNotificationsModule } from '../../../node_modules/angular2-notifications'; 9 | 10 | describe('UpdaterService', () => { 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | SimpleNotificationsModule.forRoot() 15 | ], 16 | providers: [ 17 | UpdaterService, 18 | {provide: ElectronService, useClass: MockElectron}, 19 | {provide: StatusBarService, useClass: MockStatusBar}, 20 | ] 21 | }); 22 | }); 23 | 24 | it('should be created', inject([UpdaterService], (service: UpdaterService) => { 25 | expect(service).toBeTruthy(); 26 | })); 27 | }); 28 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/add-comment-prompt/add-comment-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Enter new comment for {{key}}
3 |
4 | 5 |
6 | Submit 7 | Cancel 8 |
-------------------------------------------------------------------------------- /app/frontend/src/app/jira/add-comment-prompt/add-comment-prompt.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/jira/add-comment-prompt/add-comment-prompt.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/jira/add-comment-prompt/add-comment-prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AddCommentPromptComponent } from './add-comment-prompt.component'; 4 | import { JiraIntegrationService } from '../services/jira-integration.service'; 5 | import { MockJira } from '../../core/mocks/mock-jira-service'; 6 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 7 | 8 | describe('AddCommentPromptComponent', () => { 9 | let component: AddCommentPromptComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | FormsModule 16 | ], 17 | declarations: [ AddCommentPromptComponent ], 18 | providers: [ 19 | {provide: JiraIntegrationService, useClass: MockJira} 20 | ] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(AddCommentPromptComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/add-comment-prompt/add-comment-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; 2 | import { JiraIntegrationService } from '../services/jira-integration.service'; 3 | import { Prompt } from '../../infrastructure/prompt'; 4 | 5 | @Component({ 6 | selector: 'app-add-comment-prompt', 7 | templateUrl: './add-comment-prompt.component.html', 8 | styleUrls: ['./add-comment-prompt.component.scss'] 9 | }) 10 | export class AddCommentPromptComponent implements OnInit, Prompt { 11 | 12 | toClose: EventEmitter<{}> = new EventEmitter(); 13 | key: string; 14 | 15 | @Output() canceling = new EventEmitter(); 16 | private comment = ""; 17 | constructor( 18 | private jira: JiraIntegrationService 19 | ) { } 20 | 21 | ngOnInit() { 22 | } 23 | 24 | enter() { 25 | this.jira.addComment(this.key, this.comment); 26 | this.toClose.emit(); 27 | } 28 | 29 | close() { 30 | this.toClose.emit(); 31 | this.canceling.emit(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/jira-rich-text/jira-rich-text.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /app/frontend/src/app/jira/jira-rich-text/jira-rich-text.component.scss: -------------------------------------------------------------------------------- 1 | .rich-text-container { 2 | width: 100%; 3 | max-height: 200px; 4 | overflow-y: auto; 5 | overflow-x: hidden; 6 | background: rgba(0, 0, 0, 0.2); 7 | user-select: initial; 8 | } -------------------------------------------------------------------------------- /app/frontend/src/app/jira/jira-rich-text/jira-rich-text.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { JiraRichTextComponent } from './jira-rich-text.component'; 4 | import { JiraIntegrationService } from '../services/jira-integration.service'; 5 | import { MockJira } from '../../core/mocks/mock-jira-service'; 6 | 7 | describe('JiraRichTextComponent', () => { 8 | let component: JiraRichTextComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ JiraRichTextComponent ], 14 | providers: [ 15 | {provide: JiraIntegrationService, useClass: MockJira} 16 | ] 17 | }) 18 | .compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(JiraRichTextComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/key-selector/key-selector.component.scss: -------------------------------------------------------------------------------- 1 | .key-selector-container{ 2 | cursor: pointer; 3 | position: relative; 4 | .form-control{ 5 | padding: 0 5px; 6 | } 7 | .edit-actions{ 8 | position: absolute; 9 | padding: 3px; 10 | border-radius: 3px; 11 | background: rgba(0, 0, 0, 0.2); 12 | right: 0; 13 | top: 25px; 14 | } 15 | .issues{ 16 | z-index: 1; 17 | position: absolute; 18 | display: none; 19 | top: 25px; 20 | width: 100%; 21 | background: #FFF; 22 | border-radius: 3px; 23 | border: solid 1px var(--gray); 24 | color: var(--gray-dark); 25 | overflow: hidden; 26 | &.toggled{ 27 | display: block; 28 | } 29 | .issue{ 30 | width: 100%; 31 | &:hover{ 32 | background: rgba(0, 0, 0, 0.2); 33 | } 34 | } 35 | .issue-text{ 36 | white-space: nowrap; 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | } 40 | } 41 | } 42 | 43 | .flex-no-shrink{ 44 | flex-shrink: 0; 45 | } -------------------------------------------------------------------------------- /app/frontend/src/app/jira/key-selector/key-selector.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { KeySelectorComponent } from './key-selector.component'; 4 | import { JiraIntegrationService } from '../services/jira-integration.service'; 5 | import { MockJira } from '../../core/mocks/mock-jira-service'; 6 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 7 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 8 | 9 | describe('KeySelectorComponent', () => { 10 | let component: KeySelectorComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | FormsModule 17 | ], 18 | declarations: [KeySelectorComponent], 19 | providers: [ 20 | { provide: JiraIntegrationService, useClass: MockJira } 21 | ], 22 | schemas: [NO_ERRORS_SCHEMA] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(KeySelectorComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/comment.ts: -------------------------------------------------------------------------------- 1 | import { Profile } from "./profile"; 2 | 3 | export interface Comment { 4 | author: Profile; 5 | body: string; 6 | updateAuthor: Profile; 7 | created: Date; 8 | updated: Date; 9 | 10 | updatedString?: string; 11 | } 12 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/commit-message.ts: -------------------------------------------------------------------------------- 1 | export interface CommitMessage { 2 | message: string; 3 | detail: string; 4 | } 5 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/issue-type.ts: -------------------------------------------------------------------------------- 1 | export interface IssueType { 2 | name: string; 3 | iconUrl: string; 4 | subtask: boolean; 5 | id: string; 6 | safeIconUrl?: string; 7 | } 8 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/keyed-item.ts: -------------------------------------------------------------------------------- 1 | export interface KeyedItem { 2 | key: string; 3 | id: string; 4 | self: string; 5 | } 6 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/priority.ts: -------------------------------------------------------------------------------- 1 | export interface Priority { 2 | iconUrl: string; 3 | name: string; 4 | safeIconUrl?: string; 5 | } 6 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/profile.ts: -------------------------------------------------------------------------------- 1 | import { SafeUrl } from "@angular/platform-browser"; 2 | 3 | export interface Profile { 4 | emailAddress: string; 5 | avatarUrls: { 6 | "48x48": string; 7 | "24x24": string; 8 | "16x16": string; 9 | "32x32": string; 10 | }; 11 | name: string; 12 | key: string; 13 | displayName: string; 14 | active: boolean; 15 | safeAvatarUrl?: SafeUrl; 16 | } 17 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/resolution.ts: -------------------------------------------------------------------------------- 1 | export interface Resolution { 2 | self: string; 3 | description: string; 4 | iconUrl: string; 5 | name: string; 6 | } 7 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/status.ts: -------------------------------------------------------------------------------- 1 | export interface Status { 2 | name: string; 3 | statusCategory: { 4 | key: string; 5 | colorName: string; 6 | name: string; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/subtask.ts: -------------------------------------------------------------------------------- 1 | import { KeyedItem } from "./keyed-item"; 2 | import { IssueType } from "./issue-type"; 3 | import { Priority } from "./priority"; 4 | import { Status } from "./status"; 5 | import { SafeResourceUrl } from "@angular/platform-browser"; 6 | import { Transition } from "./transition"; 7 | 8 | export interface Subtask extends KeyedItem { 9 | fields: { 10 | summary: string, 11 | status: Status; 12 | priority: Priority; 13 | issuetype: IssueType; 14 | safePriorityIconUrl?: SafeResourceUrl; 15 | }; 16 | transitions: Transition[]; 17 | editmeta: { 18 | fields: any; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/models/transition.ts: -------------------------------------------------------------------------------- 1 | import { Status } from "./status"; 2 | 3 | export interface Transition { 4 | id: string; 5 | name: string; 6 | to: Status; 7 | hasScreen: boolean; 8 | isGlobal: boolean; 9 | isInitial: boolean; 10 | isConditional: boolean; 11 | fields: any; 12 | } 13 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/profile-selector/profile-filter.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'profileFilter', 5 | pure: false 6 | }) 7 | export class ProfileFilterPipe implements PipeTransform { 8 | transform(items: any[], filter: { name: string }): any { 9 | if (!items || !filter) { 10 | return items; 11 | } 12 | 13 | return items.filter(item => item.displayName.toUpperCase().indexOf(filter.name.toUpperCase()) !== -1 || item.key.toUpperCase().indexOf(filter.name.toUpperCase()) !== -1); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/profile-selector/profile-selector.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | {{profile.displayName}} 5 |
6 |
7 |
8 | 10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 |
20 | {{user.displayName}} 21 | @{{user.key}} 22 |
23 |
24 |
25 |
26 |
-------------------------------------------------------------------------------- /app/frontend/src/app/jira/profile-selector/profile-selector.component.scss: -------------------------------------------------------------------------------- 1 | .profile-container { 2 | position: relative; 3 | .name-container { 4 | overflow: hidden; 5 | white-space: nowrap; 6 | text-overflow: ellipsis; 7 | } 8 | cursor: pointer; 9 | .profiles { 10 | z-index: 1; 11 | min-width: 130px; 12 | display: none; 13 | position: absolute; 14 | top: 35px; 15 | background: #FFF; 16 | border-radius: 5px; 17 | right: 0; 18 | min-width: 100%; 19 | min-height: 20px; 20 | max-height: 300px; 21 | overflow-y: auto; 22 | border: solid 2px var(--gray); 23 | &.toggled { 24 | display: block; 25 | } 26 | .user { 27 | color: var(--gray-dark); 28 | cursor: pointer; 29 | img { 30 | border-radius: 50%; 31 | } 32 | &:hover { 33 | background: rgba(0, 0, 0, 0.2); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/frontend/src/app/jira/profile-selector/profile-selector.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileSelectorComponent } from './profile-selector.component'; 4 | import { JiraIntegrationService } from '../services/jira-integration.service'; 5 | import { MockJira } from '../../core/mocks/mock-jira-service'; 6 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 7 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 8 | 9 | describe('ProfileSelectorComponent', () => { 10 | let component: ProfileSelectorComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | FormsModule 17 | ], 18 | declarations: [ProfileSelectorComponent], 19 | providers: [ 20 | { provide: JiraIntegrationService, useClass: MockJira } 21 | ], 22 | schemas: [NO_ERRORS_SCHEMA] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(ProfileSelectorComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/resolution-control/resolution-control.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{resolution.name}} 3 |
4 |
5 | {{res.name}} 6 |
7 |
8 |
9 | This field is not editable 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/resolution-control/resolution-control.component.scss: -------------------------------------------------------------------------------- 1 | .resolution-container { 2 | position: relative; 3 | cursor: pointer; 4 | .transitions { 5 | z-index: 1; 6 | min-width: 130px; 7 | display: none; 8 | position: absolute; 9 | top: 35px; 10 | background: #FFF; 11 | border-radius: 5px; 12 | width: 100%; 13 | right: 0; 14 | min-height: 20px; 15 | border: solid 2px var(--gray); 16 | &.toggled { 17 | display: block; 18 | } 19 | .transition { 20 | cursor: pointer; 21 | &:hover { 22 | color: #FFF !important; 23 | background: var(--gray); 24 | } 25 | } 26 | } 27 | } 28 | a{ 29 | &:hover{ 30 | text-decoration: none; 31 | } 32 | } -------------------------------------------------------------------------------- /app/frontend/src/app/jira/resolution-control/resolution-control.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; 2 | import { Resolution } from '../models/resolution'; 3 | import { JiraIntegrationService } from '../services/jira-integration.service'; 4 | 5 | @Component({ 6 | selector: 'app-resolution-control', 7 | templateUrl: './resolution-control.component.html', 8 | styleUrls: ['./resolution-control.component.scss'] 9 | }) 10 | export class ResolutionControlComponent implements OnInit { 11 | 12 | @Input() resolution: Resolution; 13 | @Input() key: string; 14 | @Input() editable = true; 15 | @Output() resolutionSelected: EventEmitter = new EventEmitter(); 16 | private resolutions: Resolution[]; 17 | private toggled = false; 18 | constructor( 19 | private jira: JiraIntegrationService 20 | ) { 21 | this.resolutions = this.jira.resolutions; 22 | } 23 | 24 | ngOnInit() { 25 | } 26 | 27 | selectResolution(resolution: Resolution) { 28 | this.jira.updateIssue(this.key, { "resolution": { "name": resolution.name } }); 29 | this.resolutionSelected.emit(resolution); 30 | } 31 | toggle() { 32 | if (this.editable) { 33 | this.toggled = !this.toggled; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/resolution-selector/resolution-selector.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Select resolution for {{key}}
3 |
4 |
5 | 8 |
9 |
10 | Submit 11 | Continue Without Resolution 12 | Cancel 13 |
-------------------------------------------------------------------------------- /app/frontend/src/app/jira/resolution-selector/resolution-selector.component.scss: -------------------------------------------------------------------------------- 1 | select { 2 | height: 30px; 3 | 4 | } -------------------------------------------------------------------------------- /app/frontend/src/app/jira/resolution-selector/resolution-selector.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ResolutionSelectorComponent } from './resolution-selector.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | import { JiraIntegrationService } from '../services/jira-integration.service'; 6 | import { MockJira } from '../../core/mocks/mock-jira-service'; 7 | import { SimpleNotificationsModule } from '../../../../node_modules/angular2-notifications'; 8 | 9 | describe('ResolutionSelectorComponent', () => { 10 | let component: ResolutionSelectorComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | FormsModule, 17 | ], 18 | declarations: [ ResolutionSelectorComponent ], 19 | providers: [ 20 | {provide: JiraIntegrationService, useClass: MockJira} 21 | ] 22 | }) 23 | .compileComponents(); 24 | })); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(ResolutionSelectorComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/resolution-selector/resolution-selector.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter } from '@angular/core'; 2 | import { Prompt } from '../../infrastructure/prompt'; 3 | import { JiraIntegrationService } from '../services/jira-integration.service'; 4 | import { Resolution } from '../models/resolution'; 5 | 6 | @Component({ 7 | selector: 'app-resolution-selector', 8 | templateUrl: './resolution-selector.component.html', 9 | styleUrls: ['./resolution-selector.component.scss'] 10 | }) 11 | export class ResolutionSelectorComponent implements OnInit, Prompt { 12 | 13 | toClose = new EventEmitter(); 14 | toEnter = new EventEmitter(); 15 | key = ""; 16 | required = false; 17 | private _resolution; 18 | private resolutions: Resolution[] = []; 19 | constructor( 20 | private jira: JiraIntegrationService 21 | ) { 22 | this.resolutions = this.jira.resolutions; 23 | } 24 | 25 | ngOnInit() { 26 | } 27 | 28 | enter() { 29 | this.toEnter.emit(this._resolution); 30 | this.toClose.emit(); 31 | } 32 | continue() { 33 | this.toEnter.emit(""); 34 | this.toClose.emit(); 35 | } 36 | close() { 37 | this.toClose.emit(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/services/jira-issue-link-guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; 2 | import { Injectable } from "@angular/core"; 3 | import { JiraIntegrationService } from "./jira-integration.service"; 4 | 5 | @Injectable() 6 | export class JIRAIssueGuard implements CanActivate { 7 | constructor( 8 | private jira: JiraIntegrationService, 9 | ) { 10 | } 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 12 | this.jira.pushPrevious(route.params.previousKey); 13 | this.jira.navigateToIssue(route.params.key); 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/subtask-prompt/subtask-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 |
New subtask for {{key}}
3 |
4 | 5 |
6 | Create 7 | Cancel 8 |
-------------------------------------------------------------------------------- /app/frontend/src/app/jira/subtask-prompt/subtask-prompt.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/jira/subtask-prompt/subtask-prompt.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/jira/subtask-prompt/subtask-prompt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SubtaskPromptComponent } from './subtask-prompt.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | 6 | describe('SubtaskPromptComponent', () => { 7 | let component: SubtaskPromptComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | FormsModule 14 | ], 15 | declarations: [ SubtaskPromptComponent ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(SubtaskPromptComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/subtask-prompt/subtask-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter } from '@angular/core'; 2 | import { Prompt } from '../../infrastructure/prompt'; 3 | 4 | @Component({ 5 | selector: 'app-subtask-prompt', 6 | templateUrl: './subtask-prompt.component.html', 7 | styleUrls: ['./subtask-prompt.component.scss'] 8 | }) 9 | export class SubtaskPromptComponent implements OnInit, Prompt { 10 | 11 | toClose = new EventEmitter(); 12 | toEnter = new EventEmitter(); 13 | toCancel = new EventEmitter(); 14 | key = ""; 15 | private name = ""; 16 | constructor() { } 17 | 18 | ngOnInit() { 19 | } 20 | 21 | enter() { 22 | this.toEnter.emit(this.name); 23 | this.toClose.emit(); 24 | } 25 | 26 | close() { 27 | this.toCancel.emit(); 28 | this.toClose.emit(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/title-editor/title-editor.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{text}}
3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
-------------------------------------------------------------------------------- /app/frontend/src/app/jira/title-editor/title-editor.component.scss: -------------------------------------------------------------------------------- 1 | .title-container { 2 | width: 100%; 3 | position: relative; 4 | border-radius: 5px; 5 | padding: 3px; 6 | cursor: pointer; 7 | h5 i { 8 | display: none; 9 | } 10 | &:hover { 11 | background: rgba(0, 0, 0, 0.2); 12 | h5 i { 13 | display: inline; 14 | } 15 | } 16 | .edit-actions { 17 | padding: 3px; 18 | background: var(--gray-dark); 19 | border-radius: 3px; 20 | right: 0; 21 | top: 38px; 22 | position: absolute; 23 | } 24 | } -------------------------------------------------------------------------------- /app/frontend/src/app/jira/title-editor/title-editor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TitleEditorComponent } from './title-editor.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | 6 | describe('TitleEditorComponent', () => { 7 | let component: TitleEditorComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | FormsModule 14 | ], 15 | declarations: [ TitleEditorComponent ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(TitleEditorComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/title-editor/title-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-title-editor', 5 | templateUrl: './title-editor.component.html', 6 | styleUrls: ['./title-editor.component.scss'] 7 | }) 8 | export class TitleEditorComponent implements OnInit { 9 | 10 | @Input() 11 | set text(txt: string) { 12 | this._text = txt; 13 | this.textChange.emit(this._text); 14 | } 15 | get text(): string { 16 | return this._text; 17 | } 18 | @Output() textChange = new EventEmitter(); 19 | @Output() toConfirm = new EventEmitter(); 20 | private _text = ""; 21 | private editText = ""; 22 | private editing = false; 23 | constructor() { } 24 | 25 | ngOnInit() { 26 | } 27 | 28 | toggleEdit() { 29 | this.editText = this._text; 30 | this.editing = true; 31 | } 32 | cancelEdit() { 33 | this.editing = false; 34 | } 35 | confirmEdit() { 36 | this.text = this.editText; 37 | this.toConfirm.emit(); 38 | this.editing = false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/frontend/src/app/jira/transition-control/transition-control.component.html: -------------------------------------------------------------------------------- 1 |
4 | {{status.name}} 5 |
6 |
11 | {{trans.name}} 12 |
13 |
14 |
-------------------------------------------------------------------------------- /app/frontend/src/app/jira/transition-control/transition-control.component.scss: -------------------------------------------------------------------------------- 1 | .status-container { 2 | position: relative; 3 | cursor: pointer; 4 | .transitions { 5 | z-index: 1; 6 | min-width: 130px; 7 | display: none; 8 | position: absolute; 9 | top: 35px; 10 | background: #FFF; 11 | border-radius: 5px; 12 | width: 100%; 13 | right: 0; 14 | min-height: 20px; 15 | border: solid 2px var(--gray); 16 | &.toggled { 17 | display: block; 18 | } 19 | .transition { 20 | cursor: pointer; 21 | &:hover { 22 | color: #FFF !important; 23 | background: var(--gray); 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/frontend/src/app/settings/auth-settings/auth-settings.component.scss: -------------------------------------------------------------------------------- 1 | .input-action-btn{ 2 | cursor: pointer; 3 | } -------------------------------------------------------------------------------- /app/frontend/src/app/settings/auth-settings/auth-settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthSettingsComponent } from './auth-settings.component'; 4 | import { SettingsService } from '../services/settings.service'; 5 | import { MockSettings } from '../mocks/mock-settings-service'; 6 | import { NgbModule } from '../../../../node_modules/@ng-bootstrap/ng-bootstrap'; 7 | 8 | describe('AuthSettingsComponent', () => { 9 | let component: AuthSettingsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | NgbModule.forRoot() 16 | ], 17 | declarations: [AuthSettingsComponent], 18 | providers: [ 19 | { provide: SettingsService, useClass: MockSettings } 20 | ] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(AuthSettingsComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/auth-settings/auth-settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { SettingsService } from '../services/settings.service'; 3 | import { SettingsComponent } from '../prototypes/settings-component'; 4 | 5 | @Component({ 6 | selector: 'app-auth-settings', 7 | templateUrl: './auth-settings.component.html', 8 | styleUrls: ['./auth-settings.component.scss'] 9 | }) 10 | export class AuthSettingsComponent extends SettingsComponent { 11 | 12 | private keyPath = ""; 13 | private pubPath = ""; 14 | constructor( 15 | settings: SettingsService 16 | ) { 17 | super(settings); 18 | } 19 | 20 | getSettings() { 21 | this.keyPath = this.settings.getAppSetting('auth-keypath'); 22 | this.pubPath = this.settings.getAppSetting('auth-pubpath'); 23 | } 24 | 25 | browseKey() { 26 | this.keyPath = this.settings.browseFile(); 27 | this.settings.setSetting('auth-keypath', this.keyPath); 28 | } 29 | browsePub() { 30 | this.pubPath = this.settings.browseFile(); 31 | this.settings.setSetting('auth-pubpath', this.pubPath); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/ci-settings/ci-settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/settings/ci-settings/ci-settings.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/settings/ci-settings/ci-settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CiSettingsComponent } from './ci-settings.component'; 4 | import { SettingsService } from '../services/settings.service'; 5 | import { MockSettings } from '../mocks/mock-settings-service'; 6 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 7 | import { ElectronService } from '../../infrastructure/electron.service'; 8 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 9 | 10 | describe('CiSettingsComponent', () => { 11 | let component: CiSettingsComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [CiSettingsComponent], 17 | providers: [ 18 | { provide: SettingsService, useClass: MockSettings }, 19 | { provide: ElectronService, useClass: MockElectron } 20 | ], 21 | schemas: [NO_ERRORS_SCHEMA] 22 | }) 23 | .compileComponents(); 24 | })); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(CiSettingsComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/general-settings/general-settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/settings/general-settings/general-settings.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/settings/general-settings/general-settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { GeneralSettingsComponent } from './general-settings.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 6 | import { SettingsService } from '../services/settings.service'; 7 | import { MockSettings } from '../mocks/mock-settings-service'; 8 | 9 | describe('GeneralSettingsComponent', () => { 10 | let component: GeneralSettingsComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | FormsModule 17 | ], 18 | declarations: [GeneralSettingsComponent], 19 | providers: [ 20 | { provide: SettingsService, useClass: MockSettings } 21 | ], 22 | schemas: [NO_ERRORS_SCHEMA] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(GeneralSettingsComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/jira-settings/jira-settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/settings/jira-settings/jira-settings.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/settings/jira-settings/jira-settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { JiraSettingsComponent } from './jira-settings.component'; 4 | import { SettingsService } from '../services/settings.service'; 5 | import { MockSettings } from '../mocks/mock-settings-service'; 6 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 7 | import { ElectronService } from '../../infrastructure/electron.service'; 8 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 9 | 10 | describe('JiraSettingsComponent', () => { 11 | let component: JiraSettingsComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [JiraSettingsComponent], 17 | providers: [ 18 | { provide: SettingsService, useClass: MockSettings }, 19 | { provide: ElectronService, useClass: MockElectron } 20 | ], 21 | schemas: [NO_ERRORS_SCHEMA] 22 | }) 23 | .compileComponents(); 24 | })); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(JiraSettingsComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/mocks/mock-settings-service.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter } from "../../../../node_modules/@angular/core"; 2 | 3 | export class MockSettings { 4 | @Output() settingsUpdated = new EventEmitter(); 5 | settingsData = new Settings; 6 | constructor( 7 | ) { 8 | } 9 | 10 | init() { 11 | } 12 | setSetting(key, value) { 13 | } 14 | setRepoSetting(key, value) { 15 | } 16 | setSecureRepoSetting(key, value) { 17 | } 18 | browseFile(): string { 19 | return ""; 20 | } 21 | getRepoSetting(key) { 22 | } 23 | getSecureRepoSetting(key) { 24 | } 25 | getAppSetting(key) { 26 | } 27 | clearSecureCache() { 28 | } 29 | } 30 | 31 | class Settings { 32 | app_settings: Map; 33 | repo_settings: Map; 34 | current_repo: RepoInfo = { 35 | id: '', 36 | name: null 37 | }; 38 | } 39 | 40 | interface RepoInfo { 41 | id: string; 42 | name: string; 43 | } 44 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/profile-settings/profile-settings.component.html: -------------------------------------------------------------------------------- 1 |

2 | Profile

3 |
4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
-------------------------------------------------------------------------------- /app/frontend/src/app/settings/profile-settings/profile-settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/settings/profile-settings/profile-settings.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/settings/profile-settings/profile-settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileSettingsComponent } from './profile-settings.component'; 4 | import { SettingsService } from '../services/settings.service'; 5 | import { MockSettings } from '../mocks/mock-settings-service'; 6 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 7 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 8 | 9 | describe('ProfileSettingsComponent', () => { 10 | let component: ProfileSettingsComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [ ProfileSettingsComponent ], 16 | imports: [ 17 | FormsModule 18 | ], 19 | providers: [ 20 | { provide: SettingsService, useClass: MockSettings }, 21 | ], 22 | schemas: [NO_ERRORS_SCHEMA] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(ProfileSettingsComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/profile-settings/profile-settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { SettingsComponent } from '../prototypes/settings-component'; 3 | import { SettingsService } from '../services/settings.service'; 4 | 5 | @Component({ 6 | selector: 'app-profile-settings', 7 | templateUrl: './profile-settings.component.html', 8 | styleUrls: ['./profile-settings.component.scss'] 9 | }) 10 | export class ProfileSettingsComponent extends SettingsComponent { 11 | 12 | private email = ""; 13 | private name = ""; 14 | getSettings() { 15 | this.email = this.settings.getAppSetting('profile-email'); 16 | this.name = this.settings.getAppSetting('profile-name'); 17 | } 18 | updateSettings() { 19 | this.settings.setSetting('profile-email', this.email); 20 | this.settings.setSetting('profile-name', this.name); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/prototypes/settings-component.ts: -------------------------------------------------------------------------------- 1 | import { SettingsService } from "../services/settings.service"; 2 | import { OnInit, Component } from "@angular/core"; 3 | 4 | @Component({}) 5 | export abstract class SettingsComponent implements OnInit { 6 | 7 | constructor( 8 | protected settings: SettingsService 9 | ) { 10 | settings.settingsUpdated.subscribe(sett => { 11 | this.getSettings(); 12 | }); 13 | this.settings = settings; 14 | } 15 | abstract getSettings(); 16 | ngOnInit(): void { 17 | this.getSettings(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/repo-profile/repo-profile.component.html: -------------------------------------------------------------------------------- 1 |

2 | Repository Profile

3 |
4 |
5 |
6 | 7 | 8 |
9 | Enable Repository Profile to use a different name and email to commit to this repository. 10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 |
-------------------------------------------------------------------------------- /app/frontend/src/app/settings/repo-profile/repo-profile.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/app/settings/repo-profile/repo-profile.component.scss -------------------------------------------------------------------------------- /app/frontend/src/app/settings/repo-profile/repo-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RepoProfileComponent } from './repo-profile.component'; 4 | import { FormsModule } from '../../../../node_modules/@angular/forms'; 5 | import { SettingsService } from '../services/settings.service'; 6 | import { MockSettings } from '../mocks/mock-settings-service'; 7 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 8 | 9 | describe('RepoProfileComponent', () => { 10 | let component: RepoProfileComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [ RepoProfileComponent ], 16 | imports: [ 17 | FormsModule 18 | ], 19 | providers: [ 20 | { provide: SettingsService, useClass: MockSettings }, 21 | ], 22 | schemas: [NO_ERRORS_SCHEMA] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(RepoProfileComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/repo-profile/repo-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { SettingsComponent } from '../prototypes/settings-component'; 3 | 4 | @Component({ 5 | selector: 'app-repo-profile', 6 | templateUrl: './repo-profile.component.html', 7 | styleUrls: ['./repo-profile.component.scss'] 8 | }) 9 | export class RepoProfileComponent extends SettingsComponent { 10 | 11 | private repoProfile = false; 12 | private name = ""; 13 | private email = ""; 14 | getSettings() { 15 | this.repoProfile = this.settings.getRepoSetting('use-repo-profile'); 16 | this.name = this.settings.getRepoSetting('profile-name'); 17 | this.email = this.settings.getRepoSetting('profile-email'); 18 | } 19 | 20 | updateEnableRepoProfile(enabled) { 21 | this.repoProfile = enabled; 22 | this.settings.setRepoSetting('use-repo-profile', enabled); 23 | if (!enabled) { 24 | this.setRepoProfile(undefined, undefined); 25 | } else { 26 | this.setRepoProfile(this.name, this.email); 27 | } 28 | } 29 | 30 | updateProfile() { 31 | this.setRepoProfile(this.name, this.email); 32 | } 33 | 34 | private setRepoProfile(name, email) { 35 | this.settings.setRepoSetting('profile-name', name); 36 | this.settings.setRepoSetting('profile-email', email); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/services/settings.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { SettingsService } from './settings.service'; 4 | import { ElectronService } from '../../infrastructure/electron.service'; 5 | import { MockElectron } from '../../infrastructure/mocks/mock-electron-service'; 6 | import { SimpleNotificationsModule } from '../../../../node_modules/angular2-notifications'; 7 | 8 | describe('SettingsService', () => { 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ 12 | SimpleNotificationsModule.forRoot() 13 | ], 14 | providers: [ 15 | SettingsService, 16 | { provide: ElectronService, useClass: MockElectron }, 17 | ] 18 | }); 19 | }); 20 | 21 | it('should be created', inject([SettingsService], (service: SettingsService) => { 22 | expect(service).toBeTruthy(); 23 | })); 24 | }); 25 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/settings-nav/settings-nav.component.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #FFF; 2 | $item-height: 46px; 3 | $width: 400px; 4 | 5 | :host{ 6 | height: 100%; 7 | } 8 | .back-btn{ 9 | cursor: pointer; 10 | } 11 | 12 | #setting-nav { 13 | position:relative; 14 | width: $width; 15 | height: 100%; 16 | flex-shrink: 0; 17 | background-color: var(--gray-dark); 18 | color: $primary-color; 19 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.8); 20 | overflow: visible; 21 | } 22 | .setting-option-container{ 23 | width: 100%; 24 | padding: 10px 25px; 25 | height: $item-height; 26 | border-top: solid 1px #222; 27 | } 28 | 29 | .setting-separator{ 30 | width: 100%; 31 | padding: 2px 10px; 32 | height: 26px; 33 | font-size: 14px; 34 | color: var(--gray); 35 | 36 | // box-shadow: 37 | // inset 0px 11px 8px -12px rgba(0, 0, 0, 0.8), 38 | // inset 0px -11px 8px -12px rgba(0, 0, 0, 0.8); 39 | 40 | background-color: rgba(0,0,0,0.2); 41 | border-top: solid 1px #222; 42 | } 43 | 44 | .action-button{ 45 | cursor: pointer; 46 | &:hover{ 47 | background: rgba(0, 0, 0, 0.2); 48 | } 49 | } -------------------------------------------------------------------------------- /app/frontend/src/app/settings/settings-nav/settings-nav.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SettingsNavComponent } from './settings-nav.component'; 4 | import { RouterTestingModule } from '../../../../node_modules/@angular/router/testing'; 5 | import { SettingsService } from '../services/settings.service'; 6 | import { MockSettings } from '../mocks/mock-settings-service'; 7 | 8 | describe('SettingsNavComponent', () => { 9 | let component: SettingsNavComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | RouterTestingModule 16 | ], 17 | declarations: [ SettingsNavComponent ], 18 | providers: [ 19 | {provide: SettingsService, useClass: MockSettings} 20 | ] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(SettingsNavComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/settings-nav/settings-nav.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { SettingsService } from '../services/settings.service'; 4 | 5 | @Component({ 6 | selector: 'app-settings-nav', 7 | templateUrl: './settings-nav.component.html', 8 | styleUrls: ['./settings-nav.component.scss'] 9 | }) 10 | export class SettingsNavComponent implements OnInit { 11 | 12 | private repoName: string = null; 13 | constructor( 14 | private route: Router, 15 | private settings: SettingsService 16 | ) { 17 | settings.settingsUpdated.subscribe(val => { 18 | this.repoName = val.current_repo.name; 19 | }); 20 | if (this.settings.settingsData.current_repo) { 21 | this.repoName = settings.settingsData.current_repo.name; 22 | } 23 | } 24 | 25 | ngOnInit() { 26 | } 27 | goToGitView() { 28 | this.route.navigateByUrl('/'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/settings-page/settings-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
-------------------------------------------------------------------------------- /app/frontend/src/app/settings/settings-page/settings-page.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | width: 100%; 3 | } 4 | .settings-container{ 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | .setting-details-container{ 10 | width: 100%; 11 | } -------------------------------------------------------------------------------- /app/frontend/src/app/settings/settings-page/settings-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SettingsPageComponent } from './settings-page.component'; 4 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 5 | 6 | describe('SettingsPageComponent', () => { 7 | let component: SettingsPageComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ SettingsPageComponent ], 13 | schemas: [NO_ERRORS_SCHEMA] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(SettingsPageComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/settings-page/settings-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-settings-page', 6 | templateUrl: './settings-page.component.html', 7 | styleUrls: ['./settings-page.component.scss'] 8 | }) 9 | export class SettingsPageComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/updater/updater.component.html: -------------------------------------------------------------------------------- 1 |

2 | Update

3 |
4 |
5 |
No Update Available
6 |
Update available! Version: {{newVersion}}
7 | Download and Install Update 8 | Check for Update 9 |
10 |
11 |
12 | 13 |
14 |
15 | Checking for update... 16 |
17 |
18 |
-------------------------------------------------------------------------------- /app/frontend/src/app/settings/updater/updater.component.scss: -------------------------------------------------------------------------------- 1 | .loading-container { 2 | position: relative; 3 | width: 200px; 4 | text-align: center; 5 | } -------------------------------------------------------------------------------- /app/frontend/src/app/settings/updater/updater.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UpdaterComponent } from './updater.component'; 4 | import { UpdaterService } from '../../infrastructure/updater.service'; 5 | import { MockUpdater } from '../../infrastructure/mocks/mock-updater-service'; 6 | import { NO_ERRORS_SCHEMA } from '../../../../node_modules/@angular/core'; 7 | 8 | describe('UpdaterComponent', () => { 9 | let component: UpdaterComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ UpdaterComponent ], 15 | providers: [ 16 | {provide: UpdaterService, useClass: MockUpdater} 17 | ], 18 | schemas: [NO_ERRORS_SCHEMA] 19 | }) 20 | .compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(UpdaterComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /app/frontend/src/app/settings/updater/updater.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { UpdaterService } from '../../infrastructure/updater.service'; 3 | 4 | @Component({ 5 | selector: 'app-updater', 6 | templateUrl: './updater.component.html', 7 | styleUrls: ['./updater.component.scss'] 8 | }) 9 | export class UpdaterComponent implements OnInit { 10 | 11 | checkingUpdate = false; 12 | updateAvailable = false; 13 | newVersion = ""; 14 | constructor( 15 | private updater: UpdaterService 16 | ) { 17 | updater.updateChecking.subscribe(checking => { 18 | this.checkingUpdate = checking; 19 | }); 20 | updater.updateAvailableChange.subscribe(ava => { 21 | this.updateAvailable = ava; 22 | if (ava) { 23 | this.newVersion = this.updater.updateVersion; 24 | } 25 | }); 26 | this.updateAvailable = this.updater.isUpdateAvailable; 27 | this.newVersion = this.updater.updateVersion; 28 | } 29 | 30 | ngOnInit() { 31 | } 32 | 33 | checkUpdate() { 34 | this.updater.checkUpdate(); 35 | } 36 | installUpdate() { 37 | this.updater.installUpdate(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-Black.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-BlackItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-BoldItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-Hairline.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-Hairline.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-HairlineItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-HairlineItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-Italic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-LightItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/NotoSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/NotoSans-Bold.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/NotoSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/NotoSans-BoldItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/NotoSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/NotoSans-Italic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/icomoon.eot -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /app/frontend/src/assets/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/fonts/icomoon.woff -------------------------------------------------------------------------------- /app/frontend/src/assets/icheck/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/icheck/blue.png -------------------------------------------------------------------------------- /app/frontend/src/assets/icheck/blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/icheck/blue@2x.png -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.2.0-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.2.0-1.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.2.0-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.2.0-2.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.2.0-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.2.0-3.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.2.0-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.2.0-4.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.3.0-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.3.0-1.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.3.0-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.3.0-2.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.3.0-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.3.0-3.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.4.0-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.4.0-1.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.4.0-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.4.0-2.gif -------------------------------------------------------------------------------- /app/frontend/src/assets/release-note/MG0.4.0-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/assets/release-note/MG0.4.0-3.gif -------------------------------------------------------------------------------- /app/frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /app/frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /app/frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/frontend/src/favicon.ico -------------------------------------------------------------------------------- /app/frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Metro Git 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; 7 | import 'angular2-notifications'; 8 | 9 | if (environment.production) { 10 | enableProdMode(); 11 | } 12 | 13 | platformBrowserDynamic().bootstrapModule(AppModule) 14 | .catch(err => console.log(err)); 15 | -------------------------------------------------------------------------------- /app/frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /app/frontend/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [ 8 | "node" 9 | ] 10 | }, 11 | "exclude": [ 12 | "test.ts", 13 | "**/*.spec.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /app/frontend/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "test.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /app/frontend/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /app/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2017", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/git/external-file-view.js: -------------------------------------------------------------------------------- 1 | const { ipcMain, BrowserWindow } = require('electron'); 2 | const { requireArgParams } = require('../infrastructure/handler-helper'); 3 | const url = require('url'); 4 | const path = require('path'); 5 | var fileWatch; 6 | 7 | ipcMain.on('Repo-OpenExternalFile', requireArgParams(open, ['file', 'commit'])); 8 | 9 | function init(fw) { 10 | fileWatch = fw; 11 | } 12 | 13 | function open(event, arg) { 14 | let win = new BrowserWindow({}); 15 | win.setMenu(null); 16 | let address = url.format({ 17 | pathname: path.join(__dirname, '../frontend/dist/index.html'), 18 | hash: `/file/${arg.commit}`, 19 | protocol: 'file:', 20 | slashes: true, 21 | }); 22 | win.webContents.once('did-finish-load', () => { 23 | fileWatch.getFileDetail(arg.file, arg.commit).then(result => { 24 | win.webContents.send('Repo-FileDetailRetrieved', result); 25 | }) 26 | }) 27 | win.loadURL(address); 28 | win.maximize(); 29 | } 30 | 31 | module.exports = { 32 | init: init, 33 | } -------------------------------------------------------------------------------- /app/git/repo-helpers.js: -------------------------------------------------------------------------------- 1 | function isSSH(url) { 2 | if (url.startsWith('http://') || url.startsWith('https://')) { 3 | return false; 4 | } else { 5 | return true; 6 | } 7 | } 8 | 9 | module.exports = { 10 | isSSH: isSSH 11 | } -------------------------------------------------------------------------------- /app/git/repo-history.js: -------------------------------------------------------------------------------- 1 | const { ipcMain } = require('electron'); 2 | const { requireArgParams } = require('../infrastructure/handler-helper'); 3 | 4 | let settings; 5 | let window; 6 | 7 | ipcMain.on('Settings-Init', updateRepos); 8 | ipcMain.on('Repo-RemoveHistory', requireArgParams(removeHistory, ['workingDir'])) 9 | 10 | 11 | function updateRepos(event, arg) { 12 | let repos = settings.getRepos(); 13 | let repoHistory = repos.map(r => { 14 | return { 15 | name: r.name, 16 | path: r.workingDir 17 | } 18 | }) 19 | window.webContents.send('Repo-HistoryChanged', {history: repoHistory}); 20 | } 21 | 22 | function removeHistory(event, arg) { 23 | settings.removeRepo(arg.workingDir); 24 | updateRepos(); 25 | } 26 | 27 | function init(sett, win){ 28 | settings = sett; 29 | window = win; 30 | } 31 | 32 | module.exports = { 33 | init: init, 34 | updateRepos: updateRepos 35 | } -------------------------------------------------------------------------------- /app/infrastructure/handler-helper.js: -------------------------------------------------------------------------------- 1 | function requireArgParams(wrapped, params){ 2 | if(Array.isArray(params)) { 3 | return function() { 4 | let hasRequired = true; 5 | for(let i = 0; i < params.length; i++){ 6 | if(arguments[1][params[i]] === undefined) { 7 | hasRequired = false; 8 | } 9 | } 10 | if(hasRequired) { 11 | return wrapped.apply(this, arguments); 12 | } else { 13 | return undefined; 14 | } 15 | } 16 | } else { 17 | return function(){ 18 | return wrapped.apply(this, arguments); 19 | } 20 | } 21 | } 22 | 23 | module.exports = { 24 | requireArgParams: requireArgParams 25 | } -------------------------------------------------------------------------------- /app/infrastructure/release-note.js: -------------------------------------------------------------------------------- 1 | const { ipcMain, BrowserWindow } = require('electron'); 2 | const { requireArgParams } = require('../infrastructure/handler-helper'); 3 | const url = require('url'); 4 | const path = require('path'); 5 | var settings; 6 | 7 | function init(sett, win) { 8 | settings = sett; 9 | if(!settings.get('show-release-note')) { 10 | settings.update('show-release-note', true); 11 | win.webContents.once('did-finish-load', () => { 12 | openReleaseNote(); 13 | }) 14 | } 15 | } 16 | 17 | function openReleaseNote(){ 18 | let win = new BrowserWindow({}); 19 | win.setMenu(null); 20 | let address = url.format({ 21 | pathname: path.join(__dirname, '../frontend/dist/index.html'), 22 | hash: `/release-note`, 23 | protocol: 'file:', 24 | slashes: true, 25 | }); 26 | win.loadURL(address); 27 | win.maximize(); 28 | } 29 | 30 | function openAboutPage() { 31 | let win = new BrowserWindow({}); 32 | win.setMenu(null); 33 | let address = url.format({ 34 | pathname: path.join(__dirname, '../frontend/dist/index.html'), 35 | hash: `/about`, 36 | protocol: 'file:', 37 | slashes: true, 38 | }); 39 | win.loadURL(address); 40 | win.maximize(); 41 | } 42 | 43 | module.exports = { 44 | init: init, 45 | openReleaseNote: openReleaseNote, 46 | openAboutPage: openAboutPage 47 | } -------------------------------------------------------------------------------- /app/infrastructure/shell.js: -------------------------------------------------------------------------------- 1 | const { shell, ipcMain } = require('electron'); 2 | 3 | ipcMain.on('Shell-Open', (event, arg) => { 4 | openUrl(arg.url); 5 | }); 6 | 7 | function init() { 8 | 9 | } 10 | 11 | function openUrl(url) { 12 | shell.openExternal(url); 13 | } 14 | 15 | module.exports = { 16 | init: init 17 | } -------------------------------------------------------------------------------- /app/visual/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/.DS_Store -------------------------------------------------------------------------------- /app/visual/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-1024.png -------------------------------------------------------------------------------- /app/visual/Icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-128.png -------------------------------------------------------------------------------- /app/visual/Icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-16.png -------------------------------------------------------------------------------- /app/visual/Icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-256.png -------------------------------------------------------------------------------- /app/visual/Icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-32.png -------------------------------------------------------------------------------- /app/visual/Icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-48.png -------------------------------------------------------------------------------- /app/visual/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-512.png -------------------------------------------------------------------------------- /app/visual/Icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon-64.png -------------------------------------------------------------------------------- /app/visual/Icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon.ai -------------------------------------------------------------------------------- /app/visual/Icon16.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon16.ai -------------------------------------------------------------------------------- /app/visual/Icon48.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/Icon48.ai -------------------------------------------------------------------------------- /app/visual/MacOS/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/.DS_Store -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.icns -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/.DS_Store -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_128x128.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_16x16.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_256x256.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_32x32.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_512x512.png -------------------------------------------------------------------------------- /app/visual/MacOS/metrogit.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/MacOS/metrogit.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /app/visual/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | $blue: #007bff; 3 | $cyan: #17a2b8; 4 | $gray-100: #f8f9fa; 5 | $light: $gray-100; 6 | $body-bg: #222f3d; 7 | $body-color: $light; 8 | $font-family-sans-serif: Lato, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; 9 | $primary: $blue; -------------------------------------------------------------------------------- /app/visual/appveyor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/visual/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/app/visual/icon.ico -------------------------------------------------------------------------------- /app/visual/station-dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | cd app/frontend 2 | ng build --prod --aot=false 3 | cd ../.. 4 | yarn dist 5 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | cd app/frontend 2 | ng build --prod --aot=false 3 | cd ../.. 4 | yarn dist 5 | -------------------------------------------------------------------------------- /build/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/build/.DS_Store -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/build/icon.ico -------------------------------------------------------------------------------- /build/license_en.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ming-Hung (Michael) Lu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dev-app-update.yml: -------------------------------------------------------------------------------- 1 | owner: Yamazaki93 2 | repo: MetroGit 3 | provider: github 4 | -------------------------------------------------------------------------------- /install-dep.sh: -------------------------------------------------------------------------------- 1 | yarn install 2 | cd app/frontend 3 | yarn install -------------------------------------------------------------------------------- /misc/metrogit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/misc/metrogit.gif -------------------------------------------------------------------------------- /misc/metrogit1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/misc/metrogit1.PNG -------------------------------------------------------------------------------- /misc/metrogit2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/misc/metrogit2.PNG -------------------------------------------------------------------------------- /misc/metrogit3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/misc/metrogit3.PNG -------------------------------------------------------------------------------- /misc/metrogit4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/misc/metrogit4.PNG -------------------------------------------------------------------------------- /misc/metrogit5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yamazaki93/MetroGit/d796975e4b2530557075f24cb7e1e18c26bcaf76/misc/metrogit5.PNG -------------------------------------------------------------------------------- /publish-linux.sh: -------------------------------------------------------------------------------- 1 | cd app/frontend 2 | ng build --prod --aot=false 3 | cd ../.. 4 | 5 | electron-builder build --linux --publish always 6 | -------------------------------------------------------------------------------- /publish.ps1: -------------------------------------------------------------------------------- 1 | cd app/frontend 2 | ng build --prod --aot=false 3 | cd ../.. 4 | 5 | electron-builder build --publish always 6 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | cd app/frontend 2 | ng test --config=karma-ci.conf.js 3 | --------------------------------------------------------------------------------