summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/badges/components/badge.vue2
-rw-r--r--app/assets/javascripts/boards/index.js149
-rw-r--r--app/assets/javascripts/commons/gitlab_ui.js4
-rw-r--r--app/assets/javascripts/commons/index.js1
-rw-r--r--app/assets/javascripts/ide/components/changed_file_icon.vue20
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue3
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue10
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_review.vue13
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue8
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue6
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue52
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue17
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue8
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue6
-rw-r--r--app/assets/javascripts/ide/constants.js15
-rw-r--r--app/assets/javascripts/ide/lib/common/model.js2
-rw-r--r--app/assets/javascripts/ide/services/index.js4
-rw-r--r--app/assets/javascripts/ide/stores/actions.js8
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js8
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js6
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js12
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js26
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js11
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js20
-rw-r--r--app/assets/javascripts/ide/stores/utils.js29
-rw-r--r--app/assets/javascripts/ide/utils.js12
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue3
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue3
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue19
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart.vue391
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart_constants.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/panel_resizer.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/constants.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/issue_body.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue58
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/issues_list.vue64
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/report_issues.vue55
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/report_section.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/svg_gradient.vue37
-rw-r--r--app/assets/stylesheets/framework/common.scss7
-rw-r--r--app/assets/stylesheets/framework/header.scss3
-rw-r--r--app/assets/stylesheets/framework/typography.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss (renamed from app/assets/stylesheets/pages/repo.scss)79
-rw-r--r--app/assets/stylesheets/pages/builds.scss1
-rw-r--r--app/assets/stylesheets/pages/diff.scss1
-rw-r--r--app/assets/stylesheets/pages/graph.scss58
-rw-r--r--app/assets/stylesheets/pages/issuable.scss22
-rw-r--r--app/assets/stylesheets/pages/login.scss2
-rw-r--r--app/assets/stylesheets/snippets.scss4
55 files changed, 1011 insertions, 320 deletions
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
index b4bfaee1d85..155c348286c 100644
--- a/app/assets/javascripts/badges/components/badge.vue
+++ b/app/assets/javascripts/badges/components/badge.vue
@@ -93,7 +93,7 @@ export default {
<icon
:size="16"
class="prepend-left-8 append-right-8"
- name="doc_image"
+ name="doc-image"
aria-hidden="true"
/>
</div>
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 200d1923635..bc263cbbfea 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,5 +1,3 @@
-/* eslint-disable quote-props, comma-dangle */
-
import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
@@ -47,7 +45,7 @@ export default () => {
gl.IssueBoardsApp = new Vue({
el: $boardApp,
components: {
- 'board': gl.issueBoards.Board,
+ board: gl.issueBoards.Board,
'board-sidebar': gl.issueBoards.BoardSidebar,
BoardAddIssuesModal,
},
@@ -65,11 +63,11 @@ export default () => {
defaultAvatar: $boardApp.dataset.defaultAvatar,
},
computed: {
- detailIssueVisible () {
+ detailIssueVisible() {
return Object.keys(this.detailIssue.issue).length;
},
},
- created () {
+ created() {
gl.boardService = new BoardService({
boardsEndpoint: this.boardsEndpoint,
listsEndpoint: this.listsEndpoint,
@@ -89,15 +87,16 @@ export default () => {
eventHub.$off('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
},
- mounted () {
+ mounted() {
this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit);
this.filterManager.setup();
Store.disabled = this.disabled;
- gl.boardService.all()
+ gl.boardService
+ .all()
.then(res => res.data)
- .then((data) => {
- data.forEach((board) => {
+ .then(data => {
+ data.forEach(board => {
const list = Store.addList(board, this.defaultAvatar);
if (list.type === 'closed') {
@@ -126,7 +125,7 @@ export default () => {
newIssue.setFetchingState('subscriptions', true);
BoardService.getIssueInfo(sidebarInfoEndpoint)
.then(res => res.data)
- .then((data) => {
+ .then(data => {
newIssue.setFetchingState('subscriptions', false);
newIssue.updateData({
subscribed: data.subscribed,
@@ -159,7 +158,7 @@ export default () => {
Flash(__('An error occurred when toggling the notification subscription'));
});
}
- }
+ },
},
});
@@ -168,77 +167,81 @@ export default () => {
data: {
filters: Store.state.filters,
},
- mounted () {
+ mounted() {
gl.issueBoards.newListDropdownInit();
},
});
- gl.IssueBoardsModalAddBtn = new Vue({
- el: document.getElementById('js-add-issues-btn'),
- mixins: [modalMixin],
- data() {
- return {
- modal: ModalStore.store,
- store: Store.state,
- canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
- };
- },
- computed: {
- disabled() {
- if (!this.store) {
- return true;
- }
- return !this.store.lists.filter(list => !list.preset).length;
+ const issueBoardsModal = document.getElementById('js-add-issues-btn');
+
+ if (issueBoardsModal) {
+ gl.IssueBoardsModalAddBtn = new Vue({
+ el: issueBoardsModal,
+ mixins: [modalMixin],
+ data() {
+ return {
+ modal: ModalStore.store,
+ store: Store.state,
+ canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
+ };
},
- tooltipTitle() {
- if (this.disabled) {
- return 'Please add a list to your board first';
- }
+ computed: {
+ disabled() {
+ if (!this.store) {
+ return true;
+ }
+ return !this.store.lists.filter(list => !list.preset).length;
+ },
+ tooltipTitle() {
+ if (this.disabled) {
+ return 'Please add a list to your board first';
+ }
- return '';
+ return '';
+ },
},
- },
- watch: {
- disabled() {
+ watch: {
+ disabled() {
+ this.updateTooltip();
+ },
+ },
+ mounted() {
this.updateTooltip();
},
- },
- mounted() {
- this.updateTooltip();
- },
- methods: {
- updateTooltip() {
- const $tooltip = $(this.$refs.addIssuesButton);
-
- this.$nextTick(() => {
- if (this.disabled) {
- $tooltip.tooltip();
- } else {
- $tooltip.tooltip('dispose');
+ methods: {
+ updateTooltip() {
+ const $tooltip = $(this.$refs.addIssuesButton);
+
+ this.$nextTick(() => {
+ if (this.disabled) {
+ $tooltip.tooltip();
+ } else {
+ $tooltip.tooltip('dispose');
+ }
+ });
+ },
+ openModal() {
+ if (!this.disabled) {
+ this.toggleModal(true);
}
- });
- },
- openModal() {
- if (!this.disabled) {
- this.toggleModal(true);
- }
+ },
},
- },
- template: `
- <div class="board-extra-actions">
- <button
- class="btn btn-create prepend-left-10"
- type="button"
- data-placement="bottom"
- ref="addIssuesButton"
- :class="{ 'disabled': disabled }"
- :title="tooltipTitle"
- :aria-disabled="disabled"
- v-if="canAdminList"
- @click="openModal">
- Add issues
- </button>
- </div>
- `,
- });
+ template: `
+ <div class="board-extra-actions">
+ <button
+ class="btn btn-create prepend-left-10"
+ type="button"
+ data-placement="bottom"
+ ref="addIssuesButton"
+ :class="{ 'disabled': disabled }"
+ :title="tooltipTitle"
+ :aria-disabled="disabled"
+ v-if="canAdminList"
+ @click="openModal">
+ Add issues
+ </button>
+ </div>
+ `,
+ });
+ }
};
diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js
new file mode 100644
index 00000000000..923c036f5a4
--- /dev/null
+++ b/app/assets/javascripts/commons/gitlab_ui.js
@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import progressBar from '@gitlab-org/gitlab-ui/dist/base/progress_bar';
+
+Vue.component('gl-progress-bar', progressBar);
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index 0d2fe2925d8..ea945cd3fa5 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -3,4 +3,5 @@ import './polyfills';
import './jquery';
import './bootstrap';
import './vue';
+import './gitlab_ui';
import '../lib/utils/axios_utils';
diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue
index a4e06bbbe3c..720ae11aaa6 100644
--- a/app/assets/javascripts/ide/components/changed_file_icon.vue
+++ b/app/assets/javascripts/ide/components/changed_file_icon.vue
@@ -3,6 +3,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import { pluralize } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
+import { getCommitIconMap } from '../utils';
export default {
components: {
@@ -34,16 +35,14 @@ export default {
},
computed: {
changedIcon() {
- const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : '';
- return this.file.tempFile && !this.forceModifiedIcon
- ? `file-addition${suffix}`
- : `file-modified${suffix}`;
- },
- stagedIcon() {
- return `${this.changedIcon}-solid`;
+ const suffix = !this.file.changed && this.file.staged && !this.showStagedIcon ? '-solid' : '';
+
+ if (this.forceModifiedIcon) return `file-modified${suffix}`;
+
+ return `${getCommitIconMap(this.file).icon}${suffix}`;
},
changedIconClass() {
- return `multi-${this.changedIcon} float-left`;
+ return `ide-${this.changedIcon} float-left`;
},
tooltipTitle() {
if (!this.showTooltip) return undefined;
@@ -66,6 +65,9 @@ export default {
return undefined;
},
+ showIcon() {
+ return this.file.changed || this.file.tempFile || this.file.staged || this.file.deleted;
+ },
},
};
</script>
@@ -79,7 +81,7 @@ export default {
class="ide-file-changed-icon"
>
<icon
- v-if="file.changed || file.tempFile || file.staged"
+ v-if="showIcon"
:name="changedIcon"
:size="12"
:css-classes="changedIconClass"
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index eb7cb9745ec..a8b5c7a16d0 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -1,4 +1,5 @@
<script>
+import _ from 'underscore';
import { mapActions, mapState, mapGetters } from 'vuex';
import { sprintf, __ } from '~/locale';
import * as consts from '../../stores/modules/commit/constants';
@@ -14,7 +15,7 @@ export default {
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
- { branchName: `<strong class="monospace">${this.currentBranchId}</strong>` },
+ { branchName: `<strong class="monospace">${_.escape(this.currentBranchId)}</strong>` },
false,
);
},
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index ee21eeda3cd..391004dcd3c 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import StageButton from './stage_button.vue';
import UnstageButton from './unstage_button.vue';
import { viewerTypes } from '../../constants';
+import { getCommitIconMap } from '../../utils';
export default {
components: {
@@ -42,11 +43,12 @@ export default {
},
computed: {
iconName() {
- const prefix = this.stagedList ? '-solid' : '';
- return this.file.tempFile ? `file-addition${prefix}` : `file-modified${prefix}`;
+ const suffix = this.stagedList ? '-solid' : '';
+
+ return `${getCommitIconMap(this.file).icon}${suffix}`;
},
iconClass() {
- return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
+ return `${getCommitIconMap(this.file).class} append-right-8`;
},
fullKey() {
return `${this.keyPrefix}-${this.file.key}`;
@@ -67,6 +69,8 @@ export default {
'stageChange',
]),
openFileInEditor() {
+ if (this.file.type === 'tree') return null;
+
return this.openPendingTab({
file: this.file,
keyPrefix: this.keyPrefix,
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
index 7014b9f605e..e6044401c9f 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -56,7 +56,7 @@ export default {
>
<icon
:size="12"
- name="more"
+ name="ellipsis_h"
/>
</button>
<div class="dropdown-menu dropdown-menu-right">
diff --git a/app/assets/javascripts/ide/components/ide_review.vue b/app/assets/javascripts/ide/components/ide_review.vue
index f9978762c45..d09c99050fe 100644
--- a/app/assets/javascripts/ide/components/ide_review.vue
+++ b/app/assets/javascripts/ide/components/ide_review.vue
@@ -10,7 +10,7 @@ export default {
EditorModeDropdown,
},
computed: {
- ...mapGetters(['currentMergeRequest']),
+ ...mapGetters(['currentMergeRequest', 'activeFile']),
...mapState(['viewer', 'currentMergeRequestId']),
showLatestChangesText() {
return !this.currentMergeRequestId || this.viewer === viewerTypes.diff;
@@ -23,12 +23,20 @@ export default {
},
},
mounted() {
+ if (this.activeFile && this.activeFile.pending && !this.activeFile.deleted) {
+ this.$router.push(`/project${this.activeFile.url}`, () => {
+ this.updateViewer('editor');
+ });
+ } else if (this.activeFile && this.activeFile.deleted) {
+ this.resetOpenFiles();
+ }
+
this.$nextTick(() => {
this.updateViewer(this.currentMergeRequestId ? viewerTypes.mr : viewerTypes.diff);
});
},
methods: {
- ...mapActions(['updateViewer']),
+ ...mapActions(['updateViewer', 'resetOpenFiles']),
},
};
</script>
@@ -36,7 +44,6 @@ export default {
<template>
<ide-tree-list
:viewer-type="viewer"
- :disable-action-dropdown="true"
header-class="ide-review-header"
>
<template
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 0a95c0bb30d..e996dd9059e 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -17,14 +17,18 @@ export default {
...mapGetters(['currentProject', 'currentTree', 'activeFile']),
},
mounted() {
- if (this.activeFile && this.activeFile.pending) {
+ if (!this.activeFile) return;
+
+ if (this.activeFile.pending && !this.activeFile.deleted) {
this.$router.push(`/project${this.activeFile.url}`, () => {
this.updateViewer('editor');
});
+ } else if (this.activeFile.deleted) {
+ this.resetOpenFiles();
}
},
methods: {
- ...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry']),
+ ...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry', 'resetOpenFiles']),
},
};
</script>
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 0df99798d21..2e7226b727c 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -22,11 +22,6 @@ export default {
required: false,
default: null,
},
- disableActionDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
},
computed: {
...mapState(['currentBranchId']),
@@ -69,7 +64,6 @@ export default {
:key="file.key"
:file="file"
:level="0"
- :disable-action-dropdown="disableActionDropdown"
/>
</template>
</div>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index c29e49ba766..440e480d596 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -13,7 +13,7 @@ export default {
ItemButton,
},
props: {
- branch: {
+ type: {
type: String,
required: true,
},
@@ -45,7 +45,7 @@ export default {
},
},
methods: {
- ...mapActions(['createTempEntry', 'openNewEntryModal']),
+ ...mapActions(['createTempEntry', 'openNewEntryModal', 'deleteEntry']),
createNewItem(type) {
this.openNewEntryModal({ type, path: this.path });
this.dropdownOpen = false;
@@ -82,28 +82,40 @@ export default {
ref="dropdownMenu"
class="dropdown-menu dropdown-menu-right"
>
+ <template v-if="type === 'tree'">
+ <li>
+ <item-button
+ :label="__('New file')"
+ class="d-flex"
+ icon="doc-new"
+ icon-classes="mr-2"
+ @click="createNewItem('blob')"
+ />
+ </li>
+ <li>
+ <upload
+ :path="path"
+ @create="createTempEntry"
+ />
+ </li>
+ <li>
+ <item-button
+ :label="__('New directory')"
+ class="d-flex"
+ icon="folder-new"
+ icon-classes="mr-2"
+ @click="createNewItem('tree')"
+ />
+ </li>
+ <li class="divider"></li>
+ </template>
<li>
<item-button
- :label="__('New file')"
+ :label="__('Delete')"
class="d-flex"
- icon="doc-new"
+ icon="remove"
icon-classes="mr-2"
- @click="createNewItem('blob')"
- />
- </li>
- <li>
- <upload
- :path="path"
- @create="createTempEntry"
- />
- </li>
- <li>
- <item-button
- :label="__('New directory')"
- class="d-flex"
- icon="folder-new"
- icon-classes="mr-2"
- @click="createNewItem('tree')"
+ @click="deleteEntry(path)"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 50ab242ba2a..6f1a941fbc4 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -44,7 +44,7 @@ export default {
},
},
mounted() {
- if (this.lastOpenedFile) {
+ if (this.lastOpenedFile && this.lastOpenedFile.type !== 'tree') {
this.openPendingTab({
file: this.lastOpenedFile,
keyPrefix: this.lastOpenedFile.changed ? stageKeys.unstaged : stageKeys.staged,
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 08ee12fd98f..f9badb01535 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -87,7 +87,9 @@ export default {
this.editor.updateDimensions();
},
viewer() {
- this.createEditorInstance();
+ if (!this.file.pending) {
+ this.createEditorInstance();
+ }
},
panelResizing() {
if (!this.panelResizing) {
@@ -109,6 +111,7 @@ export default {
},
methods: {
...mapActions([
+ 'getFileData',
'getRawFileData',
'changeFileContent',
'setFileLanguage',
@@ -123,10 +126,16 @@ export default {
this.editor.clearEditor();
- this.getRawFileData({
+ this.getFileData({
path: this.file.path,
- baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
+ makeFileActive: false,
})
+ .then(() =>
+ this.getRawFileData({
+ path: this.file.path,
+ baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
+ }),
+ )
.then(() => {
this.createEditorInstance();
})
@@ -246,6 +255,8 @@ export default {
ref="editor"
:class="{
'is-readonly': isCommitModeActive,
+ 'is-deleted': file.deleted,
+ 'is-added': file.tempFile
}"
class="multi-file-editor-holder"
>
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index 3b4dd5ae9aa..eb4a927fe0d 100644
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -34,11 +34,6 @@ export default {
type: Number,
required: true,
},
- disableActionDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
},
data() {
return {
@@ -212,8 +207,7 @@ export default {
/>
</span>
<new-dropdown
- v-if="isTree && !disableActionDropdown"
- :project-id="file.projectId"
+ :type="file.type"
:branch="file.branchId"
:path="file.path"
:mouse-over="mouseOver"
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index 03772ae4a4c..db47b75ec5c 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -37,7 +37,7 @@ export default {
return this.fileHasChanged ? !this.tabMouseOver : false;
},
fileHasChanged() {
- return this.tab.changed || this.tab.tempFile || this.tab.staged;
+ return this.tab.changed || this.tab.tempFile || this.tab.staged || this.tab.deleted;
},
},
@@ -71,7 +71,8 @@ export default {
<template>
<li
:class="{
- active: tab.active
+ active: tab.active,
+ disabled: tab.pending
}"
@click="clickFile(tab)"
@mouseover="mouseOverTab"
@@ -105,7 +106,6 @@ export default {
<changed-file-icon
v-else
:file="tab"
- :force-modified-icon="true"
/>
</button>
</li>
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 45d36f6f42c..0b514f31467 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -38,3 +38,18 @@ export const stageKeys = {
unstaged: 'unstaged',
staged: 'staged',
};
+
+export const commitItemIconMap = {
+ addition: {
+ icon: 'file-addition',
+ class: 'ide-file-addition',
+ },
+ modified: {
+ icon: 'file-modified',
+ class: 'ide-file-modified',
+ },
+ deleted: {
+ icon: 'file-deletion',
+ class: 'ide-file-deletion',
+ },
+};
diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
index 78e6f632728..60bddb34977 100644
--- a/app/assets/javascripts/ide/lib/common/model.js
+++ b/app/assets/javascripts/ide/lib/common/model.js
@@ -7,7 +7,7 @@ export default class Model {
this.disposable = new Disposable();
this.file = file;
this.head = head;
- this.content = file.content !== '' ? file.content : file.raw;
+ this.content = file.content !== '' || file.deleted ? file.content : file.raw;
this.disposable.add(
(this.originalModel = monacoEditor.createModel(
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index 49a481f25d5..cb93fba1665 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -18,7 +18,7 @@ export default {
return axios
.get(file.rawPath, {
- params: { format: 'json' },
+ transformResponse: [f => f],
})
.then(({ data }) => data);
},
@@ -33,7 +33,7 @@ export default {
return axios
.get(file.rawPath.replace(`/raw/${file.branchId}/${file.path}`, `/raw/${sha}/${file.path}`), {
- params: { format: 'json' },
+ transformResponse: [f => f],
})
.then(({ data }) => data);
},
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index b5bd6f5a6bb..2765acada48 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -185,6 +185,14 @@ export const openNewEntryModal = ({ commit }, { type, path = '' }) => {
$('#ide-new-entry').modal('show');
};
+export const deleteEntry = ({ commit, dispatch, state }, path) => {
+ dispatch('burstUnusedSeal');
+ dispatch('closeFile', state.entries[path]);
+ commit(types.DELETE_ENTRY, path);
+};
+
+export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
+
export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 6c0887e11ee..b343750f789 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -61,7 +61,11 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
const file = state.entries[path];
+
+ if (file.raw || file.tempFile) return Promise.resolve();
+
commit(types.TOGGLE_LOADING, { entry: file });
+
return service
.getFileData(
`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url.replace('/-/', '/')}`,
@@ -71,7 +75,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive
setPageTitle(decodeURI(normalizedHeaders['PAGE-TITLE']));
commit(types.SET_FILE_DATA, { data, file });
- commit(types.TOGGLE_FILE_OPEN, path);
+ if (makeFileActive) commit(types.TOGGLE_FILE_OPEN, path);
if (makeFileActive) dispatch('setFileActive', path);
commit(types.TOGGLE_LOADING, { entry: file });
})
@@ -97,7 +101,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) =
service
.getRawFileData(file)
.then(raw => {
- commit(types.SET_FILE_RAW_DATA, { file, raw });
+ if (!file.tempFile) commit(types.SET_FILE_RAW_DATA, { file, raw });
if (file.mrChange && file.mrChange.new_file === false) {
service
.getBaseRawFileData(file, baseSha)
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index ffaaaabff17..acb6ef5e6d4 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -21,14 +21,12 @@ export const showTreeEntry = ({ commit, dispatch, state }, path) => {
export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
if (row.type === 'tree') {
dispatch('toggleTreeOpen', row.path);
- } else if (row.type === 'blob' && (row.opened || row.changed)) {
- if (row.changed && !row.opened) {
+ } else if (row.type === 'blob') {
+ if (!row.opened) {
commit(types.TOGGLE_FILE_OPEN, row.path);
}
dispatch('setFileActive', row.path);
- } else {
- dispatch('getFileData', { path: row.path });
}
dispatch('showTreeEntry', row.path);
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index 7828c31f20e..462ca45db9b 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -174,11 +174,13 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
dispatch('updateActivityBarView', activityBarViews.edit, { root: true });
dispatch('updateViewer', 'editor', { root: true });
- router.push(
- `/project/${rootState.currentProjectId}/blob/${getters.branchName}/-/${
- rootGetters.activeFile.path
- }`,
- );
+ if (rootGetters.activeFile) {
+ router.push(
+ `/project/${rootState.currentProjectId}/blob/${getters.branchName}/-/${
+ rootGetters.activeFile.path
+ }`,
+ );
+ }
}
})
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH))
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 3db4b2f903e..03777e6c10b 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -1,7 +1,15 @@
-import { sprintf, n__ } from '../../../../locale';
+import { sprintf, n__, __ } from '../../../../locale';
import * as consts from './constants';
const BRANCH_SUFFIX_COUNT = 5;
+const createTranslatedTextForFiles = (files, text) => {
+ if (!files.length) return null;
+
+ return sprintf(n__('%{text} %{files}', '%{text} %{files} files', files.length), {
+ files: files.reduce((acc, val) => acc.concat(val.path), []).join(', '),
+ text,
+ });
+};
export const discardDraftButtonDisabled = state =>
state.commitMessage === '' || state.submitCommitLoading;
@@ -29,14 +37,16 @@ export const branchName = (state, getters, rootState) => {
export const preBuiltCommitMessage = (state, _, rootState) => {
if (state.commitMessage) return state.commitMessage;
- const files = (rootState.stagedFiles.length
- ? rootState.stagedFiles
- : rootState.changedFiles
- ).reduce((acc, val) => acc.concat(val.path), []);
+ const files = rootState.stagedFiles.length ? rootState.stagedFiles : rootState.changedFiles;
+ const modifiedFiles = files.filter(f => !f.deleted);
+ const deletedFiles = files.filter(f => f.deleted);
- return sprintf(n__('Update %{files}', 'Update %{files} files', files.length), {
- files: files.join(', '),
- });
+ return [
+ createTranslatedTextForFiles(modifiedFiles, __('Update')),
+ createTranslatedTextForFiles(deletedFiles, __('Deleted')),
+ ]
+ .filter(t => t)
+ .join('\n');
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index 8d6f9ccaf34..dae60f4d65a 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -76,3 +76,4 @@ export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
export const OPEN_NEW_ENTRY_MODAL = 'OPEN_NEW_ENTRY_MODAL';
+export const DELETE_ENTRY = 'DELETE_ENTRY';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index f8091f5b5e0..799c2f51e8d 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
import projectMutations from './mutations/project';
import mergeRequestMutation from './mutations/merge_request';
@@ -171,6 +172,16 @@ export default {
newEntryModal: { type, path },
});
},
+ [types.DELETE_ENTRY](state, path) {
+ const entry = state.entries[path];
+ const parent = entry.parentPath
+ ? state.entries[entry.parentPath]
+ : state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
+
+ entry.deleted = true;
+ state.changedFiles = state.changedFiles.concat(entry);
+ parent.tree = parent.tree.filter(f => f.path !== entry.path);
+ },
...projectMutations,
...mergeRequestMutation,
...fileMutations,
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 46547820425..9a87d50d6d5 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -1,5 +1,6 @@
/* eslint-disable no-param-reassign */
import * as types from '../mutation_types';
+import { sortTree } from '../utils';
import { diffModes } from '../../constants';
export default {
@@ -51,9 +52,17 @@ export default {
});
},
[types.SET_FILE_RAW_DATA](state, { file, raw }) {
+ const openPendingFile = state.openFiles.find(
+ f => f.path === file.path && f.pending && !f.tempFile,
+ );
+
Object.assign(state.entries[file.path], {
raw,
});
+
+ if (openPendingFile) {
+ openPendingFile.raw = raw;
+ }
},
[types.SET_FILE_BASE_RAW_DATA](state, { file, baseRaw }) {
Object.assign(state.entries[file.path], {
@@ -109,11 +118,22 @@ export default {
},
[types.DISCARD_FILE_CHANGES](state, path) {
const stagedFile = state.stagedFiles.find(f => f.path === path);
+ const entry = state.entries[path];
+ const { deleted } = entry;
Object.assign(state.entries[path], {
content: stagedFile ? stagedFile.content : state.entries[path].raw,
changed: false,
+ deleted: false,
});
+
+ if (deleted) {
+ const parent = entry.parentPath
+ ? state.entries[entry.parentPath]
+ : state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
+
+ parent.tree = sortTree(parent.tree.concat(entry));
+ }
},
[types.ADD_FILE_TO_CHANGED](state, path) {
Object.assign(state, {
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 9e6b86dd844..bf7ab93ff5e 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -46,6 +46,7 @@ export const dataStructure = () => ({
parentPath: null,
lastOpenedAt: 0,
mrChange: null,
+ deleted: false,
});
export const decorateData = entity => {
@@ -105,15 +106,37 @@ export const setPageTitle = title => {
document.title = title;
};
+export const commitActionForFile = file => {
+ if (file.deleted) {
+ return 'delete';
+ } else if (file.tempFile) {
+ return 'create';
+ }
+
+ return 'update';
+};
+
+export const getCommitFiles = (stagedFiles, deleteTree = false) =>
+ stagedFiles.reduce((acc, file) => {
+ if ((file.deleted || deleteTree) && file.type === 'tree') {
+ return acc.concat(getCommitFiles(file.tree, true));
+ }
+
+ return acc.concat({
+ ...file,
+ deleted: deleteTree || file.deleted,
+ });
+ }, []);
+
export const createCommitPayload = ({ branch, getters, newBranch, state, rootState }) => ({
branch,
commit_message: state.commitMessage || getters.preBuiltCommitMessage,
- actions: rootState.stagedFiles.map(f => ({
- action: f.tempFile ? 'create' : 'update',
+ actions: getCommitFiles(rootState.stagedFiles).map(f => ({
+ action: commitActionForFile(f),
file_path: f.path,
content: f.content,
encoding: f.base64 ? 'base64' : 'text',
- last_commit_id: newBranch ? undefined : f.lastCommitSha,
+ last_commit_id: newBranch || f.deleted ? undefined : f.lastCommitSha,
})),
start_branch: newBranch ? rootState.currentBranchId : undefined,
});
diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js
new file mode 100644
index 00000000000..92b15cf232d
--- /dev/null
+++ b/app/assets/javascripts/ide/utils.js
@@ -0,0 +1,12 @@
+import { commitItemIconMap } from './constants';
+
+// eslint-disable-next-line import/prefer-default-export
+export const getCommitIconMap = file => {
+ if (file.deleted) {
+ return commitItemIconMap.deleted;
+ } else if (file.tempFile) {
+ return commitItemIconMap.addition;
+ }
+
+ return commitItemIconMap.modified;
+};
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index c32dc83da8e..14518f86dc7 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -1,5 +1,6 @@
<script>
import $ from 'jquery';
+import _ from 'underscore';
import JobNameComponent from './job_name_component.vue';
import JobComponent from './job_component.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
@@ -46,7 +47,7 @@ export default {
computed: {
tooltipText() {
- return `${this.job.name} - ${this.job.status.label}`;
+ return _.escape(`${this.job.name} - ${this.job.status.label}`);
},
},
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 4ec67f6c01b..1952dd453f4 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,4 +1,5 @@
<script>
+import _ from 'underscore';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import StageColumnComponent from './stage_column_component.vue';
@@ -26,7 +27,8 @@ export default {
methods: {
capitalizeStageName(name) {
- return name.charAt(0).toUpperCase() + name.slice(1);
+ const escapedName = _.escape(name);
+ return escapedName.charAt(0).toUpperCase() + escapedName.slice(1);
},
isFirstColumn(index) {
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index 8af984ef91a..84a3d58b770 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -1,4 +1,5 @@
<script>
+import _ from 'underscore';
import ActionComponent from './action_component.vue';
import JobNameComponent from './job_name_component.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
@@ -61,7 +62,7 @@ export default {
const textBuilder = [];
if (this.job.name) {
- textBuilder.push(this.job.name);
+ textBuilder.push(_.escape(this.job.name));
}
if (this.job.name && this.status.tooltip) {
@@ -69,7 +70,7 @@ export default {
}
if (this.status.tooltip) {
- textBuilder.push(`${this.job.status.tooltip}`);
+ textBuilder.push(this.job.status.tooltip);
}
return textBuilder.join(' ');
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 2c728582b7c..e7b2de52f76 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,4 +1,5 @@
<script>
+import _ from 'underscore';
import JobComponent from './job_component.vue';
import DropdownJobComponent from './dropdown_job_component.vue';
@@ -37,7 +38,7 @@ export default {
},
jobId(job) {
- return `ci-badge-${job.name}`;
+ return `ci-badge-${_.escape(job.name)}`;
},
buildConnnectorClass(index) {
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
index d335c3f55af..dc599e1b9fc 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
@@ -42,11 +42,14 @@ export default {
return this.timeEstimate - this.timeSpent;
},
timeRemainingPercent() {
- return `${Math.floor((this.timeSpent / this.timeEstimate) * 100)}%`;
+ return Math.floor((this.timeSpent / this.timeEstimate) * 100);
},
timeRemainingStatusClass() {
return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
},
+ progressBarVariant() {
+ return this.timeRemainingPercent > 100 ? 'danger' : 'primary';
+ },
},
};
</script>
@@ -62,16 +65,10 @@ export default {
data-placement="top"
role="timeRemainingDisplay"
>
- <div
- :aria-valuenow="timeRemainingPercent"
- class="meter-container"
- >
- <div
- :style="{ width: timeRemainingPercent }"
- class="meter-fill"
- >
- </div>
- </div>
+ <gl-progress-bar
+ :value="timeRemainingPercent"
+ :variant="progressBarVariant"
+ />
<div class="compare-display-container">
<div class="compare-display float-left">
<span class="compare-label">
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue
new file mode 100644
index 00000000000..3ced4eb691a
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue
@@ -0,0 +1,391 @@
+<script>
+import * as d3 from 'd3';
+import tooltip from '../directives/tooltip';
+import Icon from './icon.vue';
+import SvgGradient from './svg_gradient.vue';
+import {
+ GRADIENT_COLORS,
+ GRADIENT_OPACITY,
+ INVERSE_GRADIENT_COLORS,
+ INVERSE_GRADIENT_OPACITY,
+} from './bar_chart_constants';
+
+/**
+ * Renders a bar chart that can be dragged(scrolled) when the number
+ * of elements to renders surpasses that of the available viewport space
+ * while keeping even padding and a width of 24px (customizable)
+ *
+ * It can render data with the following format:
+ * graphData: [{
+ * name: 'element' // x domain data
+ * value: 1 // y domain data
+ * }]
+ *
+ * Used in:
+ * - Contribution analytics - all of the rows describing pushes, merge requests and issues
+ */
+
+export default {
+ directives: {
+ tooltip,
+ },
+ components: {
+ Icon,
+ SvgGradient,
+ },
+ props: {
+ graphData: {
+ type: Array,
+ required: true,
+ },
+ barWidth: {
+ type: Number,
+ required: false,
+ default: 24,
+ },
+ yAxisLabel: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ minX: -40,
+ minY: 0,
+ vbWidth: 0,
+ vbHeight: 0,
+ vpWidth: 0,
+ vpHeight: 350,
+ preserveAspectRatioType: 'xMidYMid meet',
+ containerMargin: {
+ leftRight: 30,
+ },
+ viewBoxMargin: {
+ topBottom: 150,
+ },
+ panX: 0,
+ xScale: {},
+ yScale: {},
+ zoom: {},
+ bars: {},
+ xGraphRange: 0,
+ isLoading: true,
+ paddingThreshold: 50,
+ showScrollIndicator: false,
+ showLeftScrollIndicator: false,
+ isGrabbed: false,
+ isPanAvailable: false,
+ gradientColors: GRADIENT_COLORS,
+ gradientOpacity: GRADIENT_OPACITY,
+ inverseGradientColors: INVERSE_GRADIENT_COLORS,
+ inverseGradientOpacity: INVERSE_GRADIENT_OPACITY,
+ maxTextWidth: 72,
+ rectYAxisLabelDims: {},
+ xAxisTextElements: {},
+ yAxisRectTransformPadding: 20,
+ yAxisTextTransformPadding: 10,
+ yAxisTextRotation: 90,
+ };
+ },
+ computed: {
+ svgViewBox() {
+ return `${this.minX} ${this.minY} ${this.vbWidth} ${this.vbHeight}`;
+ },
+ xAxisLocation() {
+ return `translate(${this.panX}, ${this.vbHeight})`;
+ },
+ barTranslationTransform() {
+ return `translate(${this.panX}, 0)`;
+ },
+ scrollIndicatorTransform() {
+ return `translate(${this.vbWidth - 80}, 0)`;
+ },
+ activateGrabCursor() {
+ return {
+ 'svg-graph-container-with-grab': this.isPanAvailable,
+ 'svg-graph-container-grabbed': this.isPanAvailable && this.isGrabbed,
+ };
+ },
+ yAxisLabelRectTransform() {
+ const rectWidth =
+ this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
+ const yCoord = this.vbHeight / 2 - rectWidth;
+
+ return `translate(${this.minX - this.yAxisRectTransformPadding}, ${yCoord})`;
+ },
+ yAxisLabelTextTransform() {
+ const rectWidth =
+ this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
+ const yCoord = this.vbHeight / 2 + rectWidth - 5;
+
+ return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${this.yAxisTextRotation})`;
+ },
+ },
+ mounted() {
+ if (!this.allValuesEmpty) {
+ this.draw();
+ }
+ },
+ methods: {
+ draw() {
+ // update viewport
+ this.vpWidth = this.$refs.svgContainer.clientWidth - this.containerMargin.leftRight;
+ // update viewbox
+ this.vbWidth = this.vpWidth;
+ this.vbHeight = this.vpHeight - this.viewBoxMargin.topBottom;
+ let padding = 0;
+ if (this.graphData.length * this.barWidth > this.vbWidth) {
+ this.xGraphRange = this.graphData.length * this.barWidth;
+ padding = this.calculatePadding(this.barWidth);
+ this.showScrollIndicator = true;
+ this.isPanAvailable = true;
+ } else {
+ this.xGraphRange = this.vbWidth - Math.abs(this.minX);
+ }
+
+ this.xScale = d3
+ .scaleBand()
+ .range([0, this.xGraphRange])
+ .round(true)
+ .paddingInner(padding);
+ this.yScale = d3.scaleLinear().rangeRound([this.vbHeight, 0]);
+
+ this.xScale.domain(this.graphData.map(d => d.name));
+ this.yScale.domain([0, d3.max(this.graphData.map(d => d.value))]);
+
+ // Zoom/Panning Function
+ this.zoom = d3
+ .zoom()
+ .translateExtent([[0, 0], [this.xGraphRange, this.vbHeight]])
+ .on('zoom', this.panGraph)
+ .on('end', this.removeGrabStyling);
+
+ const xAxis = d3.axisBottom().scale(this.xScale);
+
+ const yAxis = d3
+ .axisLeft()
+ .scale(this.yScale)
+ .ticks(4);
+
+ const renderedXAxis = d3
+ .select(this.$refs.baseSvg)
+ .select('.x-axis')
+ .call(xAxis);
+
+ this.xAxisTextElements = this.$refs.xAxis.querySelectorAll('text');
+
+ renderedXAxis.select('.domain').remove();
+
+ renderedXAxis
+ .selectAll('text')
+ .style('text-anchor', 'end')
+ .attr('dx', '-.3em')
+ .attr('dy', '-.95em')
+ .attr('class', 'tick-text')
+ .attr('transform', 'rotate(-90)');
+
+ renderedXAxis.selectAll('line').remove();
+
+ const { maxTextWidth } = this;
+ renderedXAxis.selectAll('text').each(function formatText() {
+ const axisText = d3.select(this);
+ let textLength = axisText.node().getComputedTextLength();
+ let textContent = axisText.text();
+ while (textLength > maxTextWidth && textContent.length > 0) {
+ textContent = textContent.slice(0, -1);
+ axisText.text(`${textContent}...`);
+ textLength = axisText.node().getComputedTextLength();
+ }
+ });
+
+ const width = this.vbWidth;
+
+ const renderedYAxis = d3
+ .select(this.$refs.baseSvg)
+ .select('.y-axis')
+ .call(yAxis);
+
+ renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) {
+ if (i > 0) {
+ d3
+ .select(this)
+ .select('line')
+ .attr('x2', width)
+ .attr('class', 'axis-tick');
+ }
+ });
+
+ // Add the panning capabilities
+ if (this.isPanAvailable) {
+ d3
+ .select(this.$refs.baseSvg)
+ .call(this.zoom)
+ .on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel
+ }
+
+ this.isLoading = false;
+ // Update the yAxisLabel coordinates
+ const labelDims = this.$refs.yAxisLabel.getBBox();
+ this.rectYAxisLabelDims = {
+ height: labelDims.width + 10,
+ };
+ },
+ panGraph() {
+ const allowedRightScroll = this.xGraphRange - this.vbWidth - this.paddingThreshold;
+ const graphMaxPan = Math.abs(d3.event.transform.x) < allowedRightScroll;
+ this.isGrabbed = true;
+ this.panX = d3.event.transform.x;
+
+ if (d3.event.transform.x === 0) {
+ this.showLeftScrollIndicator = false;
+ } else {
+ this.showLeftScrollIndicator = true;
+ this.showScrollIndicator = true;
+ }
+
+ if (!graphMaxPan) {
+ this.panX = -1 * (this.xGraphRange - this.vbWidth + this.paddingThreshold);
+ this.showScrollIndicator = false;
+ }
+ },
+ setTooltipTitle(data) {
+ return data !== null ? `${data.name}: ${data.value}` : '';
+ },
+ calculatePadding(desiredBarWidth) {
+ const widthWithMargin = this.vbWidth - Math.abs(this.minX);
+ const dividend = widthWithMargin - this.graphData.length * desiredBarWidth;
+ const divisor = widthWithMargin - desiredBarWidth;
+
+ return dividend / divisor;
+ },
+ removeGrabStyling() {
+ this.isGrabbed = false;
+ },
+ barHoveredIn(index) {
+ this.xAxisTextElements[index].classList.add('x-axis-text');
+ },
+ barHoveredOut(index) {
+ this.xAxisTextElements[index].classList.remove('x-axis-text');
+ },
+ },
+};
+</script>
+<template>
+ <div
+ ref="svgContainer"
+ :class="activateGrabCursor"
+ class="svg-graph-container"
+ >
+ <svg
+ ref="baseSvg"
+ :width="vpWidth"
+ :height="vpHeight"
+ :viewBox="svgViewBox"
+ :preserveAspectRatio="preserveAspectRatioType">
+ <g
+ ref="xAxis"
+ :transform="xAxisLocation"
+ class="x-axis"
+ />
+ <g v-if="!isLoading">
+ <template
+ v-for="(data, index) in graphData">
+ <rect
+ v-tooltip
+ :key="index"
+ :width="xScale.bandwidth()"
+ :x="xScale(data.name)"
+ :y="yScale(data.value)"
+ :height="vbHeight - yScale(data.value)"
+ :transform="barTranslationTransform"
+ :title="setTooltipTitle(data)"
+ class="bar-rect"
+ data-placement="top"
+ @mouseover="barHoveredIn(index)"
+ @mouseout="barHoveredOut(index)"
+ />
+ </template>
+ </g>
+ <rect
+ :height="vbHeight + 100"
+ transform="translate(-100, -5)"
+ width="100"
+ fill="#fff"
+ />
+ <g class="y-axis-label">
+ <line
+ :x1="0"
+ :x2="0"
+ :y1="0"
+ :y2="vbHeight"
+ transform="translate(-35, 0)"
+ stroke="black"
+ />
+ <!--Get text length and change the height of this rect accordingly-->
+ <rect
+ :height="rectYAxisLabelDims.height"
+ :transform="yAxisLabelRectTransform"
+ :width="30"
+ fill="#fff"
+ />
+ <text
+ ref="yAxisLabel"
+ :transform="yAxisLabelTextTransform"
+ >
+ {{ yAxisLabel }}
+ </text>
+ </g>
+ <g
+ class="y-axis"
+ />
+ <g v-if="showScrollIndicator">
+ <rect
+ :height="vbHeight + 100"
+ :transform="`translate(${vpWidth - 60}, -5)`"
+ width="40"
+ fill="#fff"
+ />
+ <icon
+ :x="vpWidth - 50"
+ :y="vbHeight / 2"
+ :width="14"
+ :height="14"
+ name="chevron-right"
+ class="animate-flicker"
+ />
+ </g>
+ <!--The line that shows up when the data elements surpass the available width -->
+ <g
+ v-if="showScrollIndicator"
+ :transform="scrollIndicatorTransform">
+ <rect
+ :height="vbHeight"
+ x="0"
+ y="0"
+ width="20"
+ fill="url(#shadow-gradient)"
+ />
+ </g>
+ <!--Left scroll indicator-->
+ <g
+ v-if="showLeftScrollIndicator"
+ transform="translate(0, 0)">
+ <rect
+ :height="vbHeight"
+ x="0"
+ y="0"
+ width="20"
+ fill="url(#left-shadow-gradient)"
+ />
+ </g>
+ <svg-gradient
+ :colors="gradientColors"
+ :opacity="gradientOpacity"
+ identifier-name="shadow-gradient"/>
+ <svg-gradient
+ :colors="inverseGradientColors"
+ :opacity="inverseGradientOpacity"
+ identifier-name="left-shadow-gradient"/>
+ </svg>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart_constants.js b/app/assets/javascripts/vue_shared/components/bar_chart_constants.js
new file mode 100644
index 00000000000..6957b112da6
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/bar_chart_constants.js
@@ -0,0 +1,4 @@
+export const GRADIENT_COLORS = ['#000', '#a7a7a7'];
+export const GRADIENT_OPACITY = ['0', '0.4'];
+export const INVERSE_GRADIENT_COLORS = ['#a7a7a7', '#000'];
+export const INVERSE_GRADIENT_OPACITY = ['0.4', '0'];
diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
index 8c2dcc2d902..7947ae1e4da 100644
--- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue
+++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
@@ -32,7 +32,7 @@
},
computed: {
className() {
- return `drag${this.side}`;
+ return `drag-${this.side}`;
},
cursorStyle() {
if (this.enabled) {
@@ -44,8 +44,15 @@
methods: {
resetSize(e) {
e.preventDefault();
+ this.$emit('resize-start', this.size);
+
this.size = this.startSize;
this.$emit('update:size', this.size);
+
+ // End resizing on next tick so that listeners can react to DOM changes
+ this.$nextTick(() => {
+ this.$emit('resize-end', this.size);
+ });
},
startDrag(e) {
if (this.enabled) {
@@ -84,7 +91,7 @@
<div
:class="className"
:style="cursorStyle"
- class="dragHandle"
+ class="drag-handle"
@mousedown="startDrag"
@dblclick="resetSize"
></div>
diff --git a/app/assets/javascripts/vue_shared/components/reports/constants.js b/app/assets/javascripts/vue_shared/components/reports/constants.js
new file mode 100644
index 00000000000..dbde648bfdb
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/reports/constants.js
@@ -0,0 +1,3 @@
+export const STATUS_FAILED = 'failed';
+export const STATUS_SUCCESS = 'success';
+export const STATUS_NEUTRAL = 'neutral';
diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_body.js b/app/assets/javascripts/vue_shared/components/reports/issue_body.js
new file mode 100644
index 00000000000..f2141e519da
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/reports/issue_body.js
@@ -0,0 +1,3 @@
+export const components = {};
+
+export const componentNames = {};
diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue b/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue
new file mode 100644
index 00000000000..f8189117ac3
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue
@@ -0,0 +1,58 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+
+import {
+ STATUS_FAILED,
+ STATUS_NEUTRAL,
+ STATUS_SUCCESS,
+} from '~/vue_shared/components/reports/constants';
+
+export default {
+ name: 'IssueStatusIcon',
+ components: {
+ Icon,
+ },
+ props: {
+ // failed || success
+ status: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ iconName() {
+ if (this.isStatusFailed) {
+ return 'status_failed_borderless';
+ } else if (this.isStatusSuccess) {
+ return 'status_success_borderless';
+ }
+
+ return 'status_created_borderless';
+ },
+ isStatusFailed() {
+ return this.status === STATUS_FAILED;
+ },
+ isStatusSuccess() {
+ return this.status === STATUS_SUCCESS;
+ },
+ isStatusNeutral() {
+ return this.status === STATUS_NEUTRAL;
+ },
+ },
+};
+</script>
+<template>
+ <div
+ :class="{
+ failed: isStatusFailed,
+ success: isStatusSuccess,
+ neutral: isStatusNeutral,
+ }"
+ class="report-block-list-icon"
+ >
+ <icon
+ :name="iconName"
+ :size="32"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue b/app/assets/javascripts/vue_shared/components/reports/issues_list.vue
index e1e03e39ee0..c01f77c2509 100644
--- a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue
+++ b/app/assets/javascripts/vue_shared/components/reports/issues_list.vue
@@ -1,5 +1,10 @@
<script>
-import IssuesBlock from './report_issues.vue';
+import IssuesBlock from '~/vue_shared/components/reports/report_issues.vue';
+import {
+ STATUS_SUCCESS,
+ STATUS_FAILED,
+ STATUS_NEUTRAL,
+} from '~/vue_shared/components/reports/constants';
/**
* Renders block of issues
@@ -9,6 +14,9 @@ export default {
components: {
IssuesBlock,
},
+ success: STATUS_SUCCESS,
+ failed: STATUS_FAILED,
+ neutral: STATUS_NEUTRAL,
props: {
unresolvedIssues: {
type: Array,
@@ -25,29 +33,10 @@ export default {
required: false,
default: () => [],
},
- allIssues: {
- type: Array,
- required: false,
- default: () => [],
- },
- type: {
+ component: {
type: String,
- required: true,
- },
- },
- data() {
- return {
- isFullReportVisible: false,
- };
- },
- computed: {
- unresolvedIssuesStatus() {
- return this.type === 'license' ? 'neutral' : 'failed';
- },
- },
- methods: {
- openFullReport() {
- this.isFullReportVisible = true;
+ required: false,
+ default: '',
},
},
};
@@ -57,43 +46,26 @@ export default {
<issues-block
v-if="unresolvedIssues.length"
- :type="type"
- :status="unresolvedIssuesStatus"
+ :component="component"
:issues="unresolvedIssues"
+ :status="$options.failed"
class="js-mr-code-new-issues"
/>
<issues-block
- v-if="isFullReportVisible"
- :type="type"
- :issues="allIssues"
- class="js-mr-code-all-issues"
- status="failed"
- />
-
- <issues-block
v-if="neutralIssues.length"
- :type="type"
+ :component="component"
:issues="neutralIssues"
+ :status="$options.neutral"
class="js-mr-code-non-issues"
- status="neutral"
/>
<issues-block
v-if="resolvedIssues.length"
- :type="type"
+ :component="component"
:issues="resolvedIssues"
+ :status="$options.success"
class="js-mr-code-resolved-issues"
- status="success"
/>
-
- <button
- v-if="allIssues.length && !isFullReportVisible"
- type="button"
- class="btn-link btn-blank prepend-left-10 js-expand-full-list break-link"
- @click="openFullReport"
- >
- {{ s__("ciReport|Show complete code vulnerabilities report") }}
- </button>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue b/app/assets/javascripts/vue_shared/components/reports/report_issues.vue
index ecffb02a3a0..2d1f3d82234 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue
+++ b/app/assets/javascripts/vue_shared/components/reports/report_issues.vue
@@ -1,19 +1,23 @@
<script>
-import Icon from '~/vue_shared/components/icon.vue';
+import IssueStatusIcon from '~/vue_shared/components/reports/issue_status_icon.vue';
+import { components, componentNames } from '~/vue_shared/components/reports/issue_body';
export default {
name: 'ReportIssues',
components: {
- Icon,
+ IssueStatusIcon,
+ ...components,
},
props: {
issues: {
type: Array,
required: true,
},
- type: {
+ component: {
type: String,
- required: true,
+ required: false,
+ default: '',
+ validator: value => value === '' || Object.values(componentNames).includes(value),
},
// failed || success
status: {
@@ -21,26 +25,6 @@ export default {
required: true,
},
},
- computed: {
- iconName() {
- if (this.isStatusFailed) {
- return 'status_failed_borderless';
- } else if (this.isStatusSuccess) {
- return 'status_success_borderless';
- }
-
- return 'status_created_borderless';
- },
- isStatusFailed() {
- return this.status === 'failed';
- },
- isStatusSuccess() {
- return this.status === 'success';
- },
- isStatusNeutral() {
- return this.status === 'neutral';
- },
- },
};
</script>
<template>
@@ -52,20 +36,17 @@ export default {
:key="index"
class="report-block-list-issue"
>
- <div
- :class="{
- failed: isStatusFailed,
- success: isStatusSuccess,
- neutral: isStatusNeutral,
- }"
- class="report-block-list-icon append-right-5"
- >
- <icon
- :name="iconName"
- :size="32"
- />
- </div>
+ <issue-status-icon
+ :status="issue.status || status"
+ class="append-right-5"
+ />
+ <component
+ v-if="component"
+ :is="component"
+ :issue="issue"
+ :status="issue.status || status"
+ />
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_section.vue b/app/assets/javascripts/vue_shared/components/reports/report_section.vue
index d383ed99a0c..0124d8b5bcc 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_section.vue
+++ b/app/assets/javascripts/vue_shared/components/reports/report_section.vue
@@ -21,7 +21,7 @@ export default {
required: false,
default: false,
},
- type: {
+ component: {
type: String,
required: false,
default: '',
@@ -59,11 +59,6 @@ export default {
required: false,
default: () => [],
},
- allIssues: {
- type: Array,
- required: false,
- default: () => [],
- },
infoText: {
type: [String, Boolean],
required: false,
@@ -142,18 +137,10 @@ export default {
</script>
<template>
<section class="media-section">
- <div
- class="media"
- >
- <status-icon
- :status="statusIconName"
- />
- <div
- class="media-body space-children d-flex"
- >
- <span
- class="js-code-text code-text"
- >
+ <div class="media">
+ <status-icon :status="statusIconName" />
+ <div class="media-body space-children d-flex flex-align-self-center">
+ <span class="js-code-text code-text">
{{ headerText }}
<popover
@@ -163,10 +150,12 @@ export default {
/>
</span>
+ <slot name="actionButtons"></slot>
+
<button
v-if="isCollapsible"
type="button"
- class="js-collapse-btn btn bt-default float-right btn-sm"
+ class="js-collapse-btn btn float-right btn-sm"
@click="toggleCollapsed"
>
{{ collapseText }}
@@ -183,8 +172,8 @@ export default {
<issues-list
:unresolved-issues="unresolvedIssues"
:resolved-issues="resolvedIssues"
- :all-issues="allIssues"
- :type="type"
+ :neutral-issues="neutralIssues"
+ :component="component"
/>
</slot>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/svg_gradient.vue b/app/assets/javascripts/vue_shared/components/svg_gradient.vue
new file mode 100644
index 00000000000..b61a1befcd6
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/svg_gradient.vue
@@ -0,0 +1,37 @@
+<script>
+export default {
+ props: {
+ colors: {
+ type: Array,
+ required: true,
+ },
+ opacity: {
+ type: Array,
+ required: true,
+ },
+ identifierName: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <svg
+ height="0"
+ width="0">
+ <defs>
+ <linearGradient
+ :id="identifierName">
+ <stop
+ :stop-color="colors[0]"
+ :stop-opacity="opacity[0]"
+ offset="0%" />
+ <stop
+ :stop-color="colors[1]"
+ :stop-opacity="opacity[1]"
+ offset="100%" />
+ </linearGradient>
+ </defs>
+ </svg>
+</template>
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 637587de597..2d6dba52801 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -370,11 +370,14 @@ img.emoji {
margin-right: 10px;
}
-.alert,
-.progress {
+.alert {
margin-bottom: $gl-padding;
}
+.progress {
+ height: 4px;
+}
+
.project-item-select-holder {
display: inline-block;
position: relative;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 2097bcebf69..e7e13d35d8e 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -567,9 +567,6 @@
border-bottom: 1px solid $white-normal;
.mx-auto {
- margin: 8px 0;
- text-align: center;
-
.tanuki-logo,
img {
height: 36px;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 56307777a72..a2789021ab4 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -179,6 +179,10 @@
font-weight: inherit;
}
+ a > code {
+ color: $gl-link-color;
+ }
+
dd {
margin-left: $gl-padding;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index efc54196b75..08755b4b545 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -835,3 +835,5 @@ $font-family-monospace: $monospace-font;
$input-line-height: 20px;
$btn-line-height: 20px;
$table-accent-bg: $gray-light;
+$card-border-color: $border-color;
+$card-cap-bg: $gray-light;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 2d76f0ce004..442aef124d3 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1,3 +1,6 @@
+@import 'framework/variables';
+@import 'framework/mixins';
+
.project-refs-form,
.project-refs-target-form {
display: inline-block;
@@ -74,6 +77,7 @@
.ide-file-icon-holder {
display: flex;
align-items: center;
+ color: $theme-gray-700;
}
.ide-file-changed-icon {
@@ -161,12 +165,23 @@
background-color: $white-light;
border-bottom-color: $white-light;
}
+
+ &:not(.disabled) {
+ .multi-file-tab {
+ cursor: pointer;
+ }
+ }
+
+ &.disabled {
+ .multi-file-tab-close {
+ cursor: default;
+ }
+ }
}
}
.multi-file-tab {
@include str-truncated(141px);
- cursor: pointer;
svg {
vertical-align: middle;
@@ -241,6 +256,38 @@
}
}
+ .is-deleted {
+ .editor.modified {
+ .margin-view-overlays,
+ .lines-content,
+ .decorationsOverviewRuler {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
+ .diffOverviewRuler.modified {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
+ .is-added {
+ .editor.original {
+ .margin-view-overlays,
+ .lines-content,
+ .decorationsOverviewRuler {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
+ .diffOverviewRuler.original {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
.monaco-diff-editor.vs {
.editor.modified {
box-shadow: none;
@@ -557,16 +604,21 @@
}
}
-.multi-file-addition,
-.multi-file-addition-solid {
+.ide-file-addition,
+.ide-file-addition-solid {
color: $green-500;
}
-.multi-file-modified,
-.multi-file-modified-solid {
+.ide-file-modified,
+.ide-file-modified-solid {
color: $orange-500;
}
+.ide-file-deletion,
+.ide-file-deletion-solid {
+ color: $red-500;
+}
+
.multi-file-commit-list-collapsed {
display: flex;
flex-direction: column;
@@ -781,18 +833,21 @@
}
}
-.dragHandle {
+.drag-handle {
position: absolute;
top: 0;
bottom: 0;
- width: 1px;
- background-color: $white-dark;
+ width: 4px;
+
+ &:hover {
+ background-color: $white-normal;
+ }
- &.dragright {
+ &.drag-right {
right: 0;
}
- &.dragleft {
+ &.drag-left {
left: 0;
}
}
@@ -1014,6 +1069,10 @@
.ide-new-btn {
margin-left: auto;
}
+
+ button {
+ color: $gl-text-color;
+ }
}
.ide-sidebar-branch-title {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index f030189af06..e5c38a20bf0 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -270,6 +270,7 @@
.block {
width: 100%;
+ word-break: break-word;
&:last-child {
border-bottom: 1px solid $border-gray-normal;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index b616357bb8d..591e21243ed 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -16,6 +16,7 @@
svg {
vertical-align: middle;
+ top: -1px;
}
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 84da9180f93..49d8a5d959b 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -31,3 +31,61 @@
color: $gl-text-red;
}
}
+
+.svg-graph-container {
+ width: 100%;
+
+ .axis-tick {
+ opacity: 0.4;
+ }
+
+ .tick-text {
+ fill: $gl-text-color-secondary;
+ }
+
+ .x-axis-text {
+ fill: $theme-gray-900;
+ }
+
+ .bar-rect {
+ fill: rgba($blue-500, 0.1);
+ stroke: $blue-500;
+ }
+
+ .bar-rect:hover {
+ fill: rgba($blue-700, 0.3);
+ }
+
+ .y-axis-label {
+ line {
+ stroke: $stat-graph-axis-fill;
+ }
+
+ text {
+ font-weight: bold;
+ font-size: 12px;
+ fill: $theme-gray-800;
+ }
+ }
+}
+
+.svg-graph-container-with-grab {
+ cursor: grab;
+ cursor: -webkit-grab;
+}
+
+.svg-graph-container-grabbed {
+ cursor: grabbing;
+ cursor: -webkit-grabbing;
+}
+
+@keyframes flickerAnimation {
+ 0% { opacity: 1; }
+ 50% { opacity: 0; }
+ 100% { opacity: 1; }
+}
+
+.animate-flicker {
+ animation: flickerAnimation 1.5s infinite;
+ fill: $theme-gray-500;
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 797b106de23..d5ae2b673d9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -834,17 +834,7 @@
}
.compare-meter {
- &.within_estimate {
- .meter-fill {
- background: $gl-primary;
- }
- }
-
&.over_estimate {
- .meter-fill {
- background: $red-500;
- }
-
.time-remaining,
.compare-value.spent {
color: $red-500;
@@ -852,18 +842,6 @@
}
}
- .meter-container {
- background: $border-gray-light;
- border-radius: 3px;
-
- .meter-fill {
- max-width: 100%;
- height: 5px;
- border-radius: 3px;
- background: $gl-primary;
- }
- }
-
.compare-display-container {
display: flex;
justify-content: space-between;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index c1b1d2e028d..8a4a2caa6c9 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -237,7 +237,7 @@
}
.login-page-broadcast {
- margin-top: 50px;
+ margin-top: 40px;
}
.navless-container {
diff --git a/app/assets/stylesheets/snippets.scss b/app/assets/stylesheets/snippets.scss
index 64110f9c3a0..bd777c66b56 100644
--- a/app/assets/stylesheets/snippets.scss
+++ b/app/assets/stylesheets/snippets.scss
@@ -22,8 +22,8 @@
height: 16px;
background-size: cover;
- &.gl-snippet-icon-doc_code { background-position: 0 0; }
- &.gl-snippet-icon-doc_text { background-position: 0 -16px; }
+ &.gl-snippet-icon-doc-code { background-position: 0 0; }
+ &.gl-snippet-icon-doc-text { background-position: 0 -16px; }
&.gl-snippet-icon-download { background-position: 0 -32px; }
}