summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/ide/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/ide/components')
-rw-r--r--app/assets/javascripts/ide/components/branches/search_list.vue8
-rw-r--r--app/assets/javascripts/ide/components/changed_file_icon.vue90
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue79
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue77
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue25
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue61
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue11
-rw-r--r--app/assets/javascripts/ide/components/error_message.vue6
-rw-r--r--app/assets/javascripts/ide/components/file_finder/index.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_finder/item.vue14
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue104
-rw-r--r--app/assets/javascripts/ide/components/file_templates/bar.vue80
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue123
-rw-r--r--app/assets/javascripts/ide/components/ide.vue21
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue18
-rw-r--r--app/assets/javascripts/ide/components/jobs/list.vue6
-rw-r--r--app/assets/javascripts/ide/components/jobs/stage.vue6
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue6
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue47
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue139
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue6
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue6
-rw-r--r--app/assets/javascripts/ide/components/preview/navigator.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue6
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue14
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue227
-rw-r--r--app/assets/javascripts/ide/components/repo_file_status_icon.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_loading_file.vue42
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue2
31 files changed, 704 insertions, 540 deletions
diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue
index 6db7b9d6b0e..52ccc537c9d 100644
--- a/app/assets/javascripts/ide/components/branches/search_list.vue
+++ b/app/assets/javascripts/ide/components/branches/search_list.vue
@@ -1,13 +1,11 @@
<script>
import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import Item from './item.vue';
export default {
components: {
- LoadingIcon,
Item,
Icon,
},
@@ -62,8 +60,8 @@ export default {
<div class="position-relative">
<input
ref="searchInput"
- :placeholder="__('Search branches')"
v-model="search"
+ :placeholder="__('Search branches')"
type="search"
class="form-control dropdown-input-field"
@input="searchBranches"
@@ -76,10 +74,10 @@ export default {
</div>
</div>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
+ :size="2"
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
- size="2"
/>
<ul
v-else
diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue
deleted file mode 100644
index 720ae11aaa6..00000000000
--- a/app/assets/javascripts/ide/components/changed_file_icon.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-<script>
-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: {
- Icon,
- },
- directives: {
- tooltip,
- },
- props: {
- file: {
- type: Object,
- required: true,
- },
- showTooltip: {
- type: Boolean,
- required: false,
- default: false,
- },
- showStagedIcon: {
- type: Boolean,
- required: false,
- default: false,
- },
- forceModifiedIcon: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- computed: {
- changedIcon() {
- 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 `ide-${this.changedIcon} float-left`;
- },
- tooltipTitle() {
- if (!this.showTooltip) return undefined;
-
- const type = this.file.tempFile ? 'addition' : 'modification';
-
- if (this.file.changed && !this.file.staged) {
- return sprintf(__('Unstaged %{type}'), {
- type,
- });
- } else if (!this.file.changed && this.file.staged) {
- return sprintf(__('Staged %{type}'), {
- type,
- });
- } else if (this.file.changed && this.file.staged) {
- return sprintf(__('Unstaged and staged %{type}'), {
- type: pluralize(type),
- });
- }
-
- return undefined;
- },
- showIcon() {
- return this.file.changed || this.file.tempFile || this.file.staged || this.file.deleted;
- },
- },
-};
-</script>
-
-<template>
- <span
- v-tooltip
- :title="tooltipTitle"
- data-container="body"
- data-placement="right"
- class="ide-file-changed-icon"
- >
- <icon
- v-if="showIcon"
- :name="changedIcon"
- :size="12"
- :css-classes="changedIconClass"
- />
- </span>
-</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
new file mode 100644
index 00000000000..b0e60edcbe5
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
@@ -0,0 +1,79 @@
+<script>
+import $ from 'jquery';
+import { mapActions } from 'vuex';
+import { __ } from '~/locale';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+
+export default {
+ components: {
+ FileIcon,
+ ChangedFileIcon,
+ },
+ props: {
+ activeFile: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ activeButtonText() {
+ return this.activeFile.staged ? __('Unstage') : __('Stage');
+ },
+ isStaged() {
+ return !this.activeFile.changed && this.activeFile.staged;
+ },
+ },
+ methods: {
+ ...mapActions(['stageChange', 'unstageChange']),
+ actionButtonClicked() {
+ if (this.activeFile.staged) {
+ this.unstageChange(this.activeFile.path);
+ } else {
+ this.stageChange(this.activeFile.path);
+ }
+ },
+ showDiscardModal() {
+ $(document.getElementById(`discard-file-${this.activeFile.path}`)).modal('show');
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="d-flex ide-commit-editor-header align-items-center">
+ <file-icon
+ :file-name="activeFile.name"
+ :size="16"
+ class="mr-2"
+ />
+ <strong class="mr-2">
+ {{ activeFile.path }}
+ </strong>
+ <changed-file-icon
+ :file="activeFile"
+ class="ml-0"
+ />
+ <div class="ml-auto">
+ <button
+ v-if="!isStaged"
+ type="button"
+ class="btn btn-remove btn-inverted append-right-8"
+ @click="showDiscardModal"
+ >
+ {{ __('Discard') }}
+ </button>
+ <button
+ :class="{
+ 'btn-success': !isStaged,
+ 'btn-warning': isStaged
+ }"
+ type="button"
+ class="btn btn-inverted"
+ @click="actionButtonClicked"
+ >
+ {{ activeButtonText }}
+ </button>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index d0fb0e3d99e..3e3539e364b 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -1,7 +1,9 @@
<script>
+import $ from 'jquery';
import { mapActions } from 'vuex';
import { __, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import ListItem from './list_item.vue';
@@ -9,6 +11,7 @@ export default {
components: {
Icon,
ListItem,
+ GlModal,
},
directives: {
tooltip,
@@ -56,6 +59,11 @@ export default {
type: String,
required: true,
},
+ emptyStateText: {
+ type: String,
+ required: false,
+ default: __('No changes'),
+ },
},
computed: {
titleText() {
@@ -68,11 +76,19 @@ export default {
},
},
methods: {
- ...mapActions(['stageAllChanges', 'unstageAllChanges']),
+ ...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']),
actionBtnClicked() {
this[this.action]();
+
+ $(this.$refs.actionBtn).tooltip('hide');
+ },
+ openDiscardModal() {
+ $('#discard-all-changes').modal('show');
},
},
+ discardModalText: __(
+ "You will loose all the unstaged changes you've made in this project. This action cannot be undone.",
+ ),
};
</script>
@@ -81,27 +97,32 @@ export default {
class="ide-commit-list-container"
>
<header
- class="multi-file-commit-panel-header"
+ class="multi-file-commit-panel-header d-flex mb-0"
>
<div
- class="multi-file-commit-panel-header-title"
+ class="d-flex align-items-center flex-fill"
>
<icon
v-once
:name="iconName"
:size="18"
+ class="append-right-8"
/>
- {{ titleText }}
+ <strong>
+ {{ titleText }}
+ </strong>
<div class="d-flex ml-auto">
<button
+ ref="actionBtn"
v-tooltip
- v-show="filesLength"
+ :title="actionBtnText"
+ :aria-label="actionBtnText"
+ :disabled="!filesLength"
:class="{
- 'd-flex': filesLength
+ 'disabled-content': !filesLength
}"
- :title="actionBtnText"
type="button"
- class="btn btn-default ide-staged-action-btn p-0 order-1 align-items-center"
+ class="d-flex ide-staged-action-btn p-0 border-0 align-items-center"
data-placement="bottom"
data-container="body"
data-boundary="viewport"
@@ -109,18 +130,32 @@ export default {
>
<icon
:name="actionBtnIcon"
- :size="12"
+ :size="16"
class="ml-auto mr-auto"
/>
</button>
- <span
+ <button
+ v-if="!stagedList"
+ v-tooltip
+ :title="__('Discard all changes')"
+ :aria-label="__('Discard all changes')"
+ :disabled="!filesLength"
:class="{
- 'rounded-right': !filesLength
+ 'disabled-content': !filesLength
}"
- class="ide-commit-file-count order-0 rounded-left text-center"
+ type="button"
+ class="d-flex ide-staged-action-btn p-0 border-0 align-items-center"
+ data-placement="bottom"
+ data-container="body"
+ data-boundary="viewport"
+ @click="openDiscardModal"
>
- {{ filesLength }}
- </span>
+ <icon
+ :size="16"
+ name="remove-all"
+ class="ml-auto mr-auto"
+ />
+ </button>
</div>
</div>
</header>
@@ -143,9 +178,19 @@ export default {
</ul>
<p
v-else
- class="multi-file-commit-list form-text text-muted"
+ class="multi-file-commit-list form-text text-muted text-center"
>
- {{ __('No changes') }}
+ {{ emptyStateText }}
</p>
+ <gl-modal
+ v-if="!stagedList"
+ id="discard-all-changes"
+ :footer-primary-button-text="__('Discard all changes')"
+ :header-title-text="__('Discard all unstaged changes?')"
+ footer-primary-button-variant="danger"
+ @submit="discardAllChanges"
+ >
+ {{ $options.discardModalText }}
+ </gl-modal>
</div>
</template>
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 391004dcd3c..ee0e72cd05f 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -2,6 +2,7 @@
import { mapActions } from 'vuex';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
import StageButton from './stage_button.vue';
import UnstageButton from './unstage_button.vue';
import { viewerTypes } from '../../constants';
@@ -12,6 +13,7 @@ export default {
Icon,
StageButton,
UnstageButton,
+ FileIcon,
},
directives: {
tooltip,
@@ -48,7 +50,7 @@ export default {
return `${getCommitIconMap(this.file).icon}${suffix}`;
},
iconClass() {
- return `${getCommitIconMap(this.file).class} append-right-8`;
+ return `${getCommitIconMap(this.file).class} ml-auto mr-auto`;
},
fullKey() {
return `${this.keyPrefix}-${this.file.key}`;
@@ -105,17 +107,20 @@ export default {
@click="openFileInEditor"
>
<span class="multi-file-commit-list-file-path d-flex align-items-center">
- <icon
- :name="iconName"
- :size="16"
- :css-classes="iconClass"
+ <file-icon
+ :file-name="file.name"
+ class="append-right-8"
/>{{ file.name }}
</span>
+ <div class="ml-auto d-flex align-items-center">
+ <div class="d-flex align-items-center ide-commit-list-changed-icon">
+ <icon
+ :name="iconName"
+ :size="16"
+ :css-classes="iconClass"
+ />
+ </div>
+ </div>
</div>
- <component
- :is="actionComponent"
- :path="file.path"
- class="d-flex position-absolute"
- />
</div>
</template>
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 e6044401c9f..8a1836a5c92 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -1,11 +1,15 @@
<script>
+import $ from 'jquery';
import { mapActions } from 'vuex';
+import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
export default {
components: {
Icon,
+ GlModal,
},
directives: {
tooltip,
@@ -16,8 +20,22 @@ export default {
required: true,
},
},
+ computed: {
+ modalId() {
+ return `discard-file-${this.path}`;
+ },
+ modalTitle() {
+ return sprintf(
+ __('Discard changes to %{path}?'),
+ { path: this.path },
+ );
+ },
+ },
methods: {
...mapActions(['stageChange', 'discardFileChanges']),
+ showDiscardModal() {
+ $(document.getElementById(this.modalId)).modal('show');
+ },
},
};
</script>
@@ -25,51 +43,50 @@ export default {
<template>
<div
v-once
- class="multi-file-discard-btn dropdown"
+ class="multi-file-discard-btn d-flex"
>
<button
v-tooltip
:aria-label="__('Stage changes')"
:title="__('Stage changes')"
type="button"
- class="btn btn-blank append-right-5 d-flex align-items-center"
+ class="btn btn-blank align-items-center"
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop="stageChange(path)"
+ @click.stop.prevent="stageChange(path)"
>
<icon
- :size="12"
+ :size="16"
name="mobile-issue-close"
+ class="ml-auto mr-auto"
/>
</button>
<button
v-tooltip
- :title="__('More actions')"
+ :aria-label="__('Discard changes')"
+ :title="__('Discard changes')"
type="button"
- class="btn btn-blank d-flex align-items-center"
+ class="btn btn-blank align-items-center"
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- data-toggle="dropdown"
- data-display="static"
+ @click.stop.prevent="showDiscardModal"
>
<icon
- :size="12"
- name="ellipsis_h"
+ :size="16"
+ name="remove"
+ class="ml-auto mr-auto"
/>
</button>
- <div class="dropdown-menu dropdown-menu-right">
- <ul>
- <li>
- <button
- type="button"
- @click.stop="discardFileChanges(path)"
- >
- {{ __('Discard changes') }}
- </button>
- </li>
- </ul>
- </div>
+ <gl-modal
+ :id="modalId"
+ :header-title-text="modalTitle"
+ :footer-primary-button-text="__('Discard changes')"
+ footer-primary-button-variant="danger"
+ @submit="discardFileChanges(path)"
+ >
+ {{ __("You will loose all changes you've made to this file. This action cannot be undone.") }}
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
index 9cec73ec00e..86c40602074 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
@@ -25,22 +25,23 @@ export default {
<template>
<div
v-once
- class="multi-file-discard-btn"
+ class="multi-file-discard-btn d-flex"
>
<button
v-tooltip
:aria-label="__('Unstage changes')"
:title="__('Unstage changes')"
type="button"
- class="btn btn-blank d-flex align-items-center"
+ class="btn btn-blank align-items-center"
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click="unstageChange(path)"
+ @click.stop.prevent="unstageChange(path)"
>
<icon
- :size="12"
- name="history"
+ :size="16"
+ name="redo"
+ class="ml-auto mr-auto"
/>
</button>
</div>
diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue
index acbc98b7a7b..a20dc0a7006 100644
--- a/app/assets/javascripts/ide/components/error_message.vue
+++ b/app/assets/javascripts/ide/components/error_message.vue
@@ -1,11 +1,7 @@
<script>
import { mapActions } from 'vuex';
-import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
- components: {
- LoadingIcon,
- },
props: {
message: {
type: Object,
@@ -59,7 +55,7 @@ export default {
@click.stop.prevent="clickAction"
>
{{ message.actionText }}
- <loading-icon
+ <gl-loading-icon
v-show="isLoading"
inline
/>
diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue
index 0ba33053717..760ed8654ee 100644
--- a/app/assets/javascripts/ide/components/file_finder/index.vue
+++ b/app/assets/javascripts/ide/components/file_finder/index.vue
@@ -174,8 +174,8 @@ export default {
<div class="dropdown-input">
<input
ref="searchInput"
- :placeholder="__('Search files')"
v-model="searchText"
+ :placeholder="__('Search files')"
type="search"
class="dropdown-input-field"
autocomplete="off"
diff --git a/app/assets/javascripts/ide/components/file_finder/item.vue b/app/assets/javascripts/ide/components/file_finder/item.vue
index f5252ce7706..72ce37be63a 100644
--- a/app/assets/javascripts/ide/components/file_finder/item.vue
+++ b/app/assets/javascripts/ide/components/file_finder/item.vue
@@ -1,7 +1,7 @@
<script>
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import FileIcon from '../../../vue_shared/components/file_icon.vue';
-import ChangedFileIcon from '../changed_file_icon.vue';
+import ChangedFileIcon from '../../../vue_shared/components/changed_file_icon.vue';
const MAX_PATH_LENGTH = 60;
@@ -78,10 +78,10 @@ export default {
class="diff-changed-file-name"
>
<span
- v-for="(char, index) in file.name.split('')"
- :key="index + char"
+ v-for="(char, charIndex) in file.name.split('')"
+ :key="charIndex + char"
:class="{
- highlighted: nameSearchTextOccurences.indexOf(index) >= 0,
+ highlighted: nameSearchTextOccurences.indexOf(charIndex) >= 0,
}"
v-text="char"
>
@@ -91,10 +91,10 @@ export default {
class="diff-changed-file-path prepend-top-5"
>
<span
- v-for="(char, index) in pathWithEllipsis.split('')"
- :key="index + char"
+ v-for="(char, charIndex) in pathWithEllipsis.split('')"
+ :key="charIndex + char"
:class="{
- highlighted: pathSearchTextOccurences.indexOf(index) >= 0,
+ highlighted: pathSearchTextOccurences.indexOf(charIndex) >= 0,
}"
v-text="char"
>
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
new file mode 100644
index 00000000000..2ad14b88410
--- /dev/null
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -0,0 +1,104 @@
+<script>
+import { mapGetters } from 'vuex';
+import { n__, __, sprintf } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+import Icon from '~/vue_shared/components/icon.vue';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+import NewDropdown from './new_dropdown/index.vue';
+import MrFileIcon from './mr_file_icon.vue';
+
+export default {
+ name: 'FileRowExtra',
+ directives: {
+ tooltip,
+ },
+ components: {
+ Icon,
+ NewDropdown,
+ ChangedFileIcon,
+ MrFileIcon,
+ },
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ mouseOver: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters([
+ 'getChangesInFolder',
+ 'getUnstagedFilesCountForPath',
+ 'getStagedFilesCountForPath',
+ ]),
+ folderUnstagedCount() {
+ return this.getUnstagedFilesCountForPath(this.file.path);
+ },
+ folderStagedCount() {
+ return this.getStagedFilesCountForPath(this.file.path);
+ },
+ changesCount() {
+ return this.getChangesInFolder(this.file.path);
+ },
+ folderChangesTooltip() {
+ if (this.changesCount === 0) return undefined;
+
+ if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
+ return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
+ } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
+ return n__('%d staged change', '%d staged changes', this.folderStagedCount);
+ }
+
+ return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
+ unstaged: this.folderUnstagedCount,
+ staged: this.folderStagedCount,
+ });
+ },
+ showTreeChangesCount() {
+ return this.file.type === 'tree' && this.changesCount > 0 && !this.file.opened;
+ },
+ showChangedFileIcon() {
+ return this.file.changed || this.file.tempFile || this.file.staged;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="float-right ide-file-icon-holder">
+ <mr-file-icon
+ v-if="file.mrChange"
+ />
+ <span
+ v-if="showTreeChangesCount"
+ class="ide-tree-changes"
+ >
+ {{ changesCount }}
+ <icon
+ v-tooltip
+ :title="folderChangesTooltip"
+ :size="12"
+ data-container="body"
+ data-placement="right"
+ name="file-modified"
+ css-classes="prepend-left-5 ide-file-modified"
+ />
+ </span>
+ <changed-file-icon
+ v-else-if="showChangedFileIcon"
+ :file="file"
+ :show-tooltip="true"
+ :show-staged-icon="true"
+ :force-modified-icon="true"
+ />
+ <new-dropdown
+ :type="file.type"
+ :path="file.path"
+ :mouse-over="mouseOver"
+ class="prepend-left-8"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/file_templates/bar.vue b/app/assets/javascripts/ide/components/file_templates/bar.vue
new file mode 100644
index 00000000000..23be5f45f16
--- /dev/null
+++ b/app/assets/javascripts/ide/components/file_templates/bar.vue
@@ -0,0 +1,80 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import Dropdown from './dropdown.vue';
+
+export default {
+ components: {
+ Dropdown,
+ },
+ computed: {
+ ...mapGetters(['activeFile']),
+ ...mapGetters('fileTemplates', ['templateTypes']),
+ ...mapState('fileTemplates', ['selectedTemplateType', 'updateSuccess']),
+ showTemplatesDropdown() {
+ return Object.keys(this.selectedTemplateType).length > 0;
+ },
+ },
+ watch: {
+ activeFile: 'setInitialType',
+ },
+ mounted() {
+ this.setInitialType();
+ },
+ methods: {
+ ...mapActions('fileTemplates', [
+ 'setSelectedTemplateType',
+ 'fetchTemplate',
+ 'undoFileTemplate',
+ ]),
+ setInitialType() {
+ const initialTemplateType = this.templateTypes.find(t => t.name === this.activeFile.name);
+
+ if (initialTemplateType) {
+ this.setSelectedTemplateType(initialTemplateType);
+ }
+ },
+ selectTemplateType(templateType) {
+ this.setSelectedTemplateType(templateType);
+ },
+ selectTemplate(template) {
+ this.fetchTemplate(template);
+ },
+ undo() {
+ this.undoFileTemplate();
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="d-flex align-items-center ide-file-templates">
+ <strong class="append-right-default">
+ {{ __('File templates') }}
+ </strong>
+ <dropdown
+ :data="templateTypes"
+ :label="selectedTemplateType.name || __('Choose a type...')"
+ class="mr-2"
+ @click="selectTemplateType"
+ />
+ <dropdown
+ v-if="showTemplatesDropdown"
+ :label="__('Choose a template...')"
+ :is-async-data="true"
+ :searchable="true"
+ :title="__('File templates')"
+ class="mr-2"
+ @click="selectTemplate"
+ />
+ <transition name="fade">
+ <button
+ v-show="updateSuccess"
+ type="button"
+ class="btn btn-default"
+ @click="undo"
+ >
+ {{ __('Undo') }}
+ </button>
+ </transition>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
new file mode 100644
index 00000000000..ef1f6de3a86
--- /dev/null
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -0,0 +1,123 @@
+<script>
+import $ from 'jquery';
+import { mapActions, mapState } from 'vuex';
+import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
+
+export default {
+ components: {
+ DropdownButton,
+ },
+ props: {
+ data: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ isAsyncData: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ searchable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ search: '',
+ };
+ },
+ computed: {
+ ...mapState('fileTemplates', ['templates', 'isLoading']),
+ outputData() {
+ return (this.isAsyncData ? this.templates : this.data).filter(t => {
+ if (!this.searchable) return true;
+
+ return t.name.toLowerCase().indexOf(this.search.toLowerCase()) >= 0;
+ });
+ },
+ showLoading() {
+ return this.isAsyncData ? this.isLoading : false;
+ },
+ },
+ mounted() {
+ $(this.$el).on('show.bs.dropdown', this.fetchTemplatesIfAsync);
+ },
+ beforeDestroy() {
+ $(this.$el).off('show.bs.dropdown', this.fetchTemplatesIfAsync);
+ },
+ methods: {
+ ...mapActions('fileTemplates', ['fetchTemplateTypes']),
+ fetchTemplatesIfAsync() {
+ if (this.isAsyncData) {
+ this.fetchTemplateTypes();
+ }
+ },
+ clickItem(item) {
+ this.$emit('click', item);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown">
+ <dropdown-button
+ :toggle-text="label"
+ data-display="static"
+ />
+ <div class="dropdown-menu pb-0">
+ <div
+ v-if="title"
+ class="dropdown-title ml-0 mr-0"
+ >
+ {{ title }}
+ </div>
+ <div
+ v-if="!showLoading && searchable"
+ class="dropdown-input"
+ >
+ <input
+ v-model="search"
+ :placeholder="__('Filter...')"
+ type="search"
+ class="dropdown-input-field"
+ />
+ <i
+ aria-hidden="true"
+ class="fa fa-search dropdown-input-search"
+ ></i>
+ </div>
+ <div class="dropdown-content">
+ <gl-loading-icon
+ v-if="showLoading"
+ :size="2"
+ />
+ <ul v-else>
+ <li
+ v-for="(item, index) in outputData"
+ :key="index"
+ >
+ <button
+ type="button"
+ @click="clickItem(item)"
+ >
+ {{ item.name }}
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 6a5ab35a16a..ad6151e3bf6 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -1,4 +1,5 @@
<script>
+import Vue from 'vue';
import Mousetrap from 'mousetrap';
import { mapActions, mapState, mapGetters } from 'vuex';
import { __ } from '~/locale';
@@ -10,6 +11,7 @@ import RepoEditor from './repo_editor.vue';
import FindFile from './file_finder/index.vue';
import RightPane from './panes/right.vue';
import ErrorMessage from './error_message.vue';
+import CommitEditorHeader from './commit_sidebar/editor_header.vue';
const originalStopCallback = Mousetrap.stopCallback;
@@ -21,8 +23,15 @@ export default {
IdeStatusBar,
RepoEditor,
FindFile,
- RightPane,
ErrorMessage,
+ CommitEditorHeader,
+ },
+ props: {
+ rightPaneComponent: {
+ type: Vue.Component,
+ required: false,
+ default: () => RightPane,
+ },
},
computed: {
...mapState([
@@ -34,7 +43,7 @@ export default {
'currentProjectId',
'errorMessage',
]),
- ...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges']),
+ ...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges', 'isCommitModeActive']),
},
mounted() {
window.onbeforeunload = e => this.onBeforeUnload(e);
@@ -96,7 +105,12 @@ export default {
<template
v-if="activeFile"
>
+ <commit-editor-header
+ v-if="isCommitModeActive"
+ :active-file="activeFile"
+ />
<repo-tabs
+ v-else
:active-file="activeFile"
:files="openFiles"
:viewer="viewer"
@@ -136,7 +150,8 @@ export default {
</div>
</template>
</div>
- <right-pane
+ <component
+ :is="rightPaneComponent"
v-if="currentProjectId"
/>
</div>
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index 4771c58a11d..f99ff6d6da8 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -1,6 +1,6 @@
<script>
import { mapState, mapGetters } from 'vuex';
-import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
+import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
import IdeTree from './ide_tree.vue';
import ResizablePanel from './resizable_panel.vue';
import ActivityBar from './activity_bar.vue';
@@ -13,7 +13,7 @@ import { activityBarViews } from '../constants';
export default {
components: {
- SkeletonLoadingContainer,
+ SkeletonLoading,
ResizablePanel,
ActivityBar,
CommitSection,
@@ -56,7 +56,7 @@ export default {
:key="n"
class="multi-file-loading-container"
>
- <skeleton-loading-container />
+ <skeleton-loading />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 715dc1bfb42..a04d09ef374 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -50,7 +50,9 @@ export default {
this.stopPipelinePolling();
},
methods: {
- ...mapActions(['setRightPane']),
+ ...mapActions('rightPane', {
+ openRightPane: 'open',
+ }),
...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']),
startTimer() {
this.intervalId = setInterval(() => {
@@ -88,7 +90,7 @@ export default {
<button
type="button"
class="p-0 border-0 h-50"
- @click="setRightPane($options.rightSidebarViews.pipelines)"
+ @click="openRightPane($options.rightSidebarViews.pipelines)"
>
<ci-icon
v-tooltip
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 00ae5ea2c15..cfe25084b42 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -1,16 +1,17 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
-import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-import RepoFile from './repo_file.vue';
+import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
+import FileRow from '~/vue_shared/components/file_row.vue';
import NavDropdown from './nav_dropdown.vue';
+import FileRowExtra from './file_row_extra.vue';
export default {
components: {
Icon,
- RepoFile,
- SkeletonLoadingContainer,
+ SkeletonLoading,
NavDropdown,
+ FileRow,
},
props: {
viewerType: {
@@ -34,8 +35,9 @@ export default {
this.updateViewer(this.viewerType);
},
methods: {
- ...mapActions(['updateViewer']),
+ ...mapActions(['updateViewer', 'toggleTreeOpen']),
},
+ FileRowExtra,
};
</script>
@@ -49,7 +51,7 @@ export default {
:key="n"
class="multi-file-loading-container"
>
- <skeleton-loading-container />
+ <skeleton-loading />
</div>
</template>
<template v-else>
@@ -63,11 +65,13 @@ export default {
<div
class="ide-tree-body h-100"
>
- <repo-file
+ <file-row
v-for="file in currentTree.tree"
:key="file.key"
:file="file"
:level="0"
+ :extra-component="$options.FileRowExtra"
+ @toggleTreeOpen="toggleTreeOpen"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue
index 3b16b860ecd..acd37605d16 100644
--- a/app/assets/javascripts/ide/components/jobs/list.vue
+++ b/app/assets/javascripts/ide/components/jobs/list.vue
@@ -1,11 +1,9 @@
<script>
import { mapActions } from 'vuex';
-import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Stage from './stage.vue';
export default {
components: {
- LoadingIcon,
Stage,
},
props: {
@@ -26,10 +24,10 @@ export default {
<template>
<div>
- <loading-icon
+ <gl-loading-icon
v-if="loading && !stages.length"
+ :size="2"
class="prepend-top-default"
- size="2"
/>
<template v-else>
<stage
diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue
index 15e881b7bc8..ec168d36b9e 100644
--- a/app/assets/javascripts/ide/components/jobs/stage.vue
+++ b/app/assets/javascripts/ide/components/jobs/stage.vue
@@ -2,7 +2,6 @@
import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
-import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Item from './item.vue';
export default {
@@ -12,7 +11,6 @@ export default {
components: {
Icon,
CiIcon,
- LoadingIcon,
Item,
},
props: {
@@ -71,8 +69,8 @@ export default {
:size="24"
/>
<strong
- v-tooltip="showTooltip"
ref="stageTitle"
+ v-tooltip="showTooltip"
:title="showTooltip ? stage.name : null"
data-container="body"
class="prepend-left-8 ide-stage-title"
@@ -96,7 +94,7 @@ export default {
v-show="!stage.isCollapsed"
class="card-body"
>
- <loading-icon
+ <gl-loading-icon
v-if="showLoadingIcon"
/>
<template v-else>
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index fc612956688..c8343e77860 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -3,7 +3,6 @@ import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Item from './item.vue';
import TokenedInput from '../shared/tokened_input.vue';
@@ -14,7 +13,6 @@ const SEARCH_TYPES = [
export default {
components: {
- LoadingIcon,
TokenedInput,
Item,
Icon,
@@ -98,10 +96,10 @@ export default {
</div>
</div>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
+ :size="2"
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
- size="2"
/>
<template v-else>
<ul
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index e500ef0e1b5..bcd53ac1ba2 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -1,6 +1,7 @@
<script>
+import $ from 'jquery';
import { __ } from '~/locale';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapState, mapGetters } from 'vuex';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { modalTypes } from '../../constants';
@@ -15,6 +16,7 @@ export default {
},
computed: {
...mapState(['entryModal']),
+ ...mapGetters('fileTemplates', ['templateTypes']),
entryName: {
get() {
if (this.entryModal.type === modalTypes.rename) {
@@ -31,7 +33,9 @@ export default {
if (this.entryModal.type === modalTypes.tree) {
return __('Create new directory');
} else if (this.entryModal.type === modalTypes.rename) {
- return this.entryModal.entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
+ return this.entryModal.entry.type === modalTypes.tree
+ ? __('Rename folder')
+ : __('Rename file');
}
return __('Create new file');
@@ -40,11 +44,16 @@ export default {
if (this.entryModal.type === modalTypes.tree) {
return __('Create directory');
} else if (this.entryModal.type === modalTypes.rename) {
- return this.entryModal.entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
+ return this.entryModal.entry.type === modalTypes.tree
+ ? __('Rename folder')
+ : __('Rename file');
}
return __('Create file');
},
+ isCreatingNew() {
+ return this.entryModal.type !== modalTypes.rename;
+ },
},
methods: {
...mapActions(['createTempEntry', 'renameEntry']),
@@ -61,6 +70,14 @@ export default {
});
}
},
+ createFromTemplate(template) {
+ this.createTempEntry({
+ name: template.name,
+ type: this.entryModal.type,
+ });
+
+ $('#ide-new-entry').modal('toggle');
+ },
focusInput() {
this.$refs.fieldName.focus();
},
@@ -77,6 +94,7 @@ export default {
:header-title-text="modalTitle"
:footer-primary-button-text="buttonLabel"
footer-primary-button-variant="success"
+ modal-size="lg"
@submit="submitForm"
@open="focusInput"
@closed="closedModal"
@@ -84,16 +102,35 @@ export default {
<div
class="form-group row"
>
- <label class="label-bold col-form-label col-sm-3">
+ <label class="label-bold col-form-label col-sm-2">
{{ __('Name') }}
</label>
- <div class="col-sm-9">
+ <div class="col-sm-10">
<input
ref="fieldName"
v-model="entryName"
type="text"
class="form-control"
+ placeholder="/dir/file_name"
/>
+ <ul
+ v-if="isCreatingNew"
+ class="prepend-top-default list-inline"
+ >
+ <li
+ v-for="(template, index) in templateTypes"
+ :key="index"
+ class="list-inline-item"
+ >
+ <button
+ type="button"
+ class="btn btn-missing p-1 pr-2 pl-2"
+ @click="createFromTemplate(template)"
+ >
+ {{ template.name }}
+ </button>
+ </li>
+ </ul>
</div>
</div>
</gl-modal>
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index 79df225c432..bd07f372177 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -1,5 +1,7 @@
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
+import _ from 'underscore';
+import { __ } from '~/locale';
import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import { rightSidebarViews } from '../../constants';
@@ -21,28 +23,77 @@ export default {
MergeRequestInfo,
Clientside,
},
+ props: {
+ extensionTabs: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
computed: {
- ...mapState(['rightPane', 'currentMergeRequestId', 'clientsidePreviewEnabled']),
+ ...mapState(['currentMergeRequestId', 'clientsidePreviewEnabled']),
+ ...mapState('rightPane', ['isOpen', 'currentView']),
...mapGetters(['packageJson']),
- pipelinesActive() {
- return (
- this.rightPane === rightSidebarViews.pipelines ||
- this.rightPane === rightSidebarViews.jobsDetail
- );
- },
+ ...mapGetters('rightPane', ['isActiveView', 'isAliveView']),
showLivePreview() {
return this.packageJson && this.clientsidePreviewEnabled;
},
+ defaultTabs() {
+ return [
+ {
+ show: this.currentMergeRequestId,
+ title: __('Merge Request'),
+ views: [
+ rightSidebarViews.mergeRequestInfo,
+ ],
+ icon: 'text-description',
+ },
+ {
+ show: true,
+ title: __('Pipelines'),
+ views: [
+ rightSidebarViews.pipelines,
+ rightSidebarViews.jobsDetail,
+ ],
+ icon: 'rocket',
+ },
+ {
+ show: this.showLivePreview,
+ title: __('Live preview'),
+ views: [
+ rightSidebarViews.clientSidePreview,
+ ],
+ icon: 'live-preview',
+ },
+ ];
+ },
+ tabs() {
+ return this.defaultTabs
+ .concat(this.extensionTabs)
+ .filter(tab => tab.show);
+ },
+ tabViews() {
+ return _.flatten(this.tabs.map(tab => tab.views));
+ },
+ aliveTabViews() {
+ return this.tabViews.filter(view => this.isAliveView(view.name));
+ },
},
methods: {
- ...mapActions(['setRightPane']),
- clickTab(e, view) {
+ ...mapActions('rightPane', ['toggleOpen', 'open']),
+ clickTab(e, tab) {
e.target.blur();
- this.setRightPane(view);
+ if (this.isActiveTab(tab)) {
+ this.toggleOpen();
+ } else {
+ this.open(tab.views[0]);
+ }
+ },
+ isActiveTab(tab) {
+ return tab.views.some(view => this.isActiveView(view.name));
},
},
- rightSidebarViews,
};
</script>
@@ -51,77 +102,45 @@ export default {
class="multi-file-commit-panel ide-right-sidebar"
>
<resizable-panel
- v-if="rightPane"
+ v-show="isOpen"
:collapsible="false"
:initial-width="350"
:min-size="350"
- :class="`ide-right-sidebar-${rightPane}`"
+ :class="`ide-right-sidebar-${currentView}`"
side="right"
class="multi-file-commit-panel-inner"
>
- <component :is="rightPane" />
+ <div
+ v-for="tabView in aliveTabViews"
+ v-show="isActiveView(tabView.name)"
+ :key="tabView.name"
+ class="h-100"
+ >
+ <component :is="tabView.name" />
+ </div>
</resizable-panel>
<nav class="ide-activity-bar">
<ul class="list-unstyled">
<li
- v-if="currentMergeRequestId"
+ v-for="tab of tabs"
+ :key="tab.title"
>
<button
v-tooltip
- :title="__('Merge Request')"
- :aria-label="__('Merge Request')"
- :class="{
- active: rightPane === $options.rightSidebarViews.mergeRequestInfo
- }"
- data-container="body"
- data-placement="left"
- class="ide-sidebar-link is-right"
- type="button"
- @click="clickTab($event, $options.rightSidebarViews.mergeRequestInfo)"
- >
- <icon
- :size="16"
- name="text-description"
- />
- </button>
- </li>
- <li>
- <button
- v-tooltip
- :title="__('Pipelines')"
- :aria-label="__('Pipelines')"
- :class="{
- active: pipelinesActive
- }"
- data-container="body"
- data-placement="left"
- class="ide-sidebar-link is-right"
- type="button"
- @click="clickTab($event, $options.rightSidebarViews.pipelines)"
- >
- <icon
- :size="16"
- name="rocket"
- />
- </button>
- </li>
- <li v-if="showLivePreview">
- <button
- v-tooltip
- :title="__('Live preview')"
- :aria-label="__('Live preview')"
+ :title="tab.title"
+ :aria-label="tab.title"
:class="{
- active: rightPane === $options.rightSidebarViews.clientSidePreview
+ active: isActiveTab(tab) && isOpen
}"
data-container="body"
data-placement="left"
class="ide-sidebar-link is-right"
type="button"
- @click="clickTab($event, $options.rightSidebarViews.clientSidePreview)"
+ @click="clickTab($event, tab)"
>
<icon
:size="16"
- name="live-preview"
+ :name="tab.icon"
/>
</button>
</li>
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 5757dfdc925..0a2681b7a1e 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -2,7 +2,6 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { sprintf, __ } from '../../../locale';
-import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import Tabs from '../../../vue_shared/components/tabs/tabs';
@@ -12,7 +11,6 @@ import JobsList from '../jobs/list.vue';
export default {
components: {
- LoadingIcon,
Icon,
CiIcon,
Tabs,
@@ -50,10 +48,10 @@ export default {
<template>
<div class="ide-pipeline">
- <loading-icon
+ <gl-loading-icon
v-if="showLoadingIcon"
+ :size="2"
class="prepend-top-default"
- size="2"
/>
<template v-else-if="latestPipeline !== null">
<header
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index 39a1bd1f61b..37a8ad36507 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -3,14 +3,12 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { Manager } from 'smooshpack';
import { listen } from 'codesandbox-api';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Navigator from './navigator.vue';
import { packageJsonPath } from '../../constants';
import { createPathWithExt } from '../../utils';
export default {
components: {
- LoadingIcon,
Navigator,
},
data() {
@@ -177,9 +175,9 @@ export default {
{{ s__('IDE|Get started with Live Preview') }}
</a>
</div>
- <loading-icon
+ <gl-loading-icon
v-else
- size="2"
+ :size="2"
class="align-self-center mt-auto mb-auto"
/>
</div>
diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue
index 4bf346946b6..42f23801692 100644
--- a/app/assets/javascripts/ide/components/preview/navigator.vue
+++ b/app/assets/javascripts/ide/components/preview/navigator.vue
@@ -1,12 +1,10 @@
<script>
import { listen } from 'codesandbox-api';
import Icon from '~/vue_shared/components/icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
export default {
components: {
Icon,
- LoadingIcon,
},
props: {
manager: {
@@ -138,7 +136,7 @@ export default {
class="ide-navigator-location form-control bg-white"
readonly
/>
- <loading-icon
+ <gl-loading-icon
v-if="loading"
class="position-absolute ide-preview-loading-icon"
/>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 6f1a941fbc4..d3b24c5b793 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -95,8 +95,9 @@ export default {
:file-list="changedFiles"
:action-btn-text="__('Stage all changes')"
:active-file-key="activeFileKey"
+ :empty-state-text="__('There are no unstaged changes')"
action="stageAllChanges"
- action-btn-icon="mobile-issue-close"
+ action-btn-icon="stage-all"
item-action-component="stage-button"
class="is-first"
icon-name="unstaged"
@@ -108,8 +109,9 @@ export default {
:action-btn-text="__('Unstage all changes')"
:staged-list="true"
:active-file-key="activeFileKey"
+ :empty-state-text="__('There are no staged changes')"
action="unstageAllChanges"
- action-btn-icon="history"
+ action-btn-icon="unstage-all"
item-action-component="unstage-button"
icon-name="staged"
/>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f55aa843444..b2599128213 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -6,12 +6,14 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import { activityBarViews, viewerTypes } from '../constants';
import Editor from '../lib/editor';
import ExternalLink from './external_link.vue';
+import FileTemplatesBar from './file_templates/bar.vue';
export default {
components: {
ContentViewer,
DiffViewer,
ExternalLink,
+ FileTemplatesBar,
},
props: {
file: {
@@ -20,12 +22,14 @@ export default {
},
},
computed: {
+ ...mapState('rightPane', {
+ rightPaneIsOpen: 'isOpen',
+ }),
...mapState([
'rightPanelCollapsed',
'viewer',
'panelResizing',
'currentActivityView',
- 'rightPane',
]),
...mapGetters([
'currentMergeRequest',
@@ -34,6 +38,7 @@ export default {
'isCommitModeActive',
'isReviewModeActive',
]),
+ ...mapGetters('fileTemplates', ['showFileTemplatesBar']),
shouldHideEditor() {
return this.file && this.file.binary && !this.file.content;
},
@@ -96,7 +101,7 @@ export default {
this.editor.updateDimensions();
}
},
- rightPane() {
+ rightPaneIsOpen() {
this.editor.updateDimensions();
},
},
@@ -216,7 +221,7 @@ export default {
id="ide"
class="blob-viewer-container blob-editor-container"
>
- <div class="ide-mode-tabs clearfix" >
+ <div class="ide-mode-tabs clearfix">
<ul
v-if="!shouldHideEditor && isEditModeActive"
class="nav-links float-left"
@@ -249,6 +254,9 @@ export default {
:file="file"
/>
</div>
+ <file-templates-bar
+ v-if="showFileTemplatesBar(file.name)"
+ />
<div
v-show="!shouldHideEditor && file.viewMode ==='editor'"
ref="editor"
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
deleted file mode 100644
index 110eda83bb4..00000000000
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ /dev/null
@@ -1,227 +0,0 @@
-<script>
-import { mapActions, mapGetters } from 'vuex';
-import { n__, __, sprintf } from '~/locale';
-import tooltip from '~/vue_shared/directives/tooltip';
-import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-import FileIcon from '~/vue_shared/components/file_icon.vue';
-import router from '../ide_router';
-import NewDropdown from './new_dropdown/index.vue';
-import FileStatusIcon from './repo_file_status_icon.vue';
-import ChangedFileIcon from './changed_file_icon.vue';
-import MrFileIcon from './mr_file_icon.vue';
-
-export default {
- name: 'RepoFile',
- directives: {
- tooltip,
- },
- components: {
- SkeletonLoadingContainer,
- NewDropdown,
- FileStatusIcon,
- FileIcon,
- ChangedFileIcon,
- MrFileIcon,
- Icon,
- },
- props: {
- file: {
- type: Object,
- required: true,
- },
- level: {
- type: Number,
- required: true,
- },
- },
- data() {
- return {
- mouseOver: false,
- };
- },
- computed: {
- ...mapGetters([
- 'getChangesInFolder',
- 'getUnstagedFilesCountForPath',
- 'getStagedFilesCountForPath',
- ]),
- folderUnstagedCount() {
- return this.getUnstagedFilesCountForPath(this.file.path);
- },
- folderStagedCount() {
- return this.getStagedFilesCountForPath(this.file.path);
- },
- changesCount() {
- return this.getChangesInFolder(this.file.path);
- },
- folderChangesTooltip() {
- if (this.changesCount === 0) return undefined;
-
- if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
- return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
- } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
- return n__('%d staged change', '%d staged changes', this.folderStagedCount);
- }
-
- return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
- unstaged: this.folderUnstagedCount,
- staged: this.folderStagedCount,
- });
- },
- isTree() {
- return this.file.type === 'tree';
- },
- isBlob() {
- return this.file.type === 'blob';
- },
- levelIndentation() {
- return {
- marginLeft: `${this.level * 16}px`,
- };
- },
- fileClass() {
- return {
- 'file-open': this.isBlob && this.file.opened,
- 'file-active': this.isBlob && this.file.active,
- folder: this.isTree,
- 'is-open': this.file.opened,
- };
- },
- showTreeChangesCount() {
- return this.isTree && this.changesCount > 0 && !this.file.opened;
- },
- showChangedFileIcon() {
- return this.file.changed || this.file.tempFile || this.file.staged;
- },
- },
- watch: {
- 'file.active': function fileActiveWatch(active) {
- if (this.file.type === 'blob' && active) {
- this.scrollIntoView();
- }
- },
- },
- mounted() {
- if (this.hasPathAtCurrentRoute()) {
- this.scrollIntoView(true);
- }
- },
- methods: {
- ...mapActions(['toggleTreeOpen']),
- clickFile() {
- // Manual Action if a tree is selected/opened
- if (this.isTree && this.hasUrlAtCurrentRoute()) {
- this.toggleTreeOpen(this.file.path);
- }
-
- router.push(`/project${this.file.url}`);
- },
- scrollIntoView(isInit = false) {
- const block = isInit && this.isTree ? 'center' : 'nearest';
-
- this.$el.scrollIntoView({
- behavior: 'smooth',
- block,
- });
- },
- hasPathAtCurrentRoute() {
- if (!this.$router || !this.$router.currentRoute) {
- return false;
- }
-
- // - strip route up to "/-/" and ending "/"
- const routePath = this.$router.currentRoute.path
- .replace(/^.*?[/]-[/]/g, '')
- .replace(/[/]$/g, '');
-
- // - strip ending "/"
- const filePath = this.file.path.replace(/[/]$/g, '');
-
- return filePath === routePath;
- },
- hasUrlAtCurrentRoute() {
- return this.$router.currentRoute.path === `/project${this.file.url}`;
- },
- toggleHover(over) {
- this.mouseOver = over;
- },
- },
-};
-</script>
-
-<template>
- <div>
- <div
- :class="fileClass"
- class="file"
- role="button"
- @click="clickFile"
- @mouseover="toggleHover(true)"
- @mouseout="toggleHover(false)"
- >
- <div
- class="file-name"
- >
- <span
- :style="levelIndentation"
- class="ide-file-name str-truncated"
- >
- <file-icon
- :file-name="file.name"
- :loading="file.loading"
- :folder="isTree"
- :opened="file.opened"
- :size="16"
- />
- {{ file.name }}
- <file-status-icon
- :file="file"
- />
- </span>
- <span class="float-right ide-file-icon-holder">
- <mr-file-icon
- v-if="file.mrChange"
- />
- <span
- v-if="showTreeChangesCount"
- class="ide-tree-changes"
- >
- {{ changesCount }}
- <icon
- v-tooltip
- :title="folderChangesTooltip"
- :size="12"
- data-container="body"
- data-placement="right"
- name="file-modified"
- css-classes="prepend-left-5 ide-file-modified"
- />
- </span>
- <changed-file-icon
- v-else-if="showChangedFileIcon"
- :file="file"
- :show-tooltip="true"
- :show-staged-icon="true"
- :force-modified-icon="true"
- class="float-right"
- />
- </span>
- <new-dropdown
- :type="file.type"
- :path="file.path"
- :mouse-over="mouseOver"
- class="float-right prepend-left-8"
- />
- </div>
- </div>
- <template v-if="file.opened">
- <repo-file
- v-for="childFile in file.tree"
- :key="childFile.key"
- :file="childFile"
- :level="level + 1"
- />
- </template>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_file_status_icon.vue b/app/assets/javascripts/ide/components/repo_file_status_icon.vue
index 76a3333be50..97589e116c5 100644
--- a/app/assets/javascripts/ide/components/repo_file_status_icon.vue
+++ b/app/assets/javascripts/ide/components/repo_file_status_icon.vue
@@ -26,8 +26,8 @@ export default {
<template>
<span
- v-tooltip
v-if="file.file_lock"
+ v-tooltip
:title="lockTooltip"
data-container="body"
>
diff --git a/app/assets/javascripts/ide/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue
deleted file mode 100644
index 7a5ede82253..00000000000
--- a/app/assets/javascripts/ide/components/repo_loading_file.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-<script>
- import { mapState } from 'vuex';
- import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-
- export default {
- components: {
- skeletonLoadingContainer,
- },
- computed: {
- ...mapState([
- 'leftPanelCollapsed',
- ]),
- },
- };
-</script>
-
-<template>
- <tr
- class="loading-file"
- aria-label="Loading files"
- >
- <td class="multi-file-table-col-name">
- <skeleton-loading-container
- :small="true"
- />
- </td>
- <template v-if="!leftPanelCollapsed">
- <td class="d-none d-sm-none d-md-block">
- <skeleton-loading-container
- :small="true"
- />
- </td>
-
- <td class="d-none d-sm-block">
- <skeleton-loading-container
- :small="true"
- class="animation-container-right"
- />
- </td>
- </template>
- </tr>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index db47b75ec5c..d621653d6fd 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -3,8 +3,8 @@ import { mapActions } from 'vuex';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import FileStatusIcon from './repo_file_status_icon.vue';
-import ChangedFileIcon from './changed_file_icon.vue';
export default {
components: {