summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorFelipe Artur <fcardozo@gitlab.com>2018-03-06 16:28:54 +0000
committerFelipe Artur <fcardozo@gitlab.com>2018-03-06 16:28:54 +0000
commite77c4e9efe0e19187929e5836cda5a3a59d0f89f (patch)
tree91daaa89bb48457456f931c6b818f5e200390b56 /app/assets
parent1e137c273ca6314d0ed6744910b95f179b1d538c (diff)
parent9a8f5a2b605f85ace3c81a32cf1855f79cabde43 (diff)
downloadgitlab-ce-e77c4e9efe0e19187929e5836cda5a3a59d0f89f.tar.gz
Merge branch 'master' into 'issue_38337'
# Conflicts: # app/models/group.rb # db/schema.rb
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js4
-rw-r--r--app/assets/javascripts/boards/index.js2
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue38
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js23
-rw-r--r--app/assets/javascripts/groups/components/app.vue10
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue65
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue35
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue36
-rw-r--r--app/assets/javascripts/ide/components/ide.vue99
-rw-r--r--app/assets/javascripts/ide/components/ide_context_bar.vue108
-rw-r--r--app/assets/javascripts/ide/components/ide_project_branches_tree.vue47
-rw-r--r--app/assets/javascripts/ide/components/ide_project_tree.vue49
-rw-r--r--app/assets/javascripts/ide/components/ide_repo_tree.vue74
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue114
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue66
-rw-r--r--app/assets/javascripts/ide/components/new_branch_form.vue108
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue101
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue112
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue87
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue171
-rw-r--r--app/assets/javascripts/ide/components/repo_edit_button.vue57
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue136
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue165
-rw-r--r--app/assets/javascripts/ide/components/repo_file_buttons.vue60
-rw-r--r--app/assets/javascripts/ide/components/repo_loading_file.vue42
-rw-r--r--app/assets/javascripts/ide/components/repo_prev_directory.vue32
-rw-r--r--app/assets/javascripts/ide/components/repo_preview.vue71
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue74
-rw-r--r--app/assets/javascripts/ide/components/repo_tabs.vue27
-rw-r--r--app/assets/javascripts/ide/ide_router.js101
-rw-r--r--app/assets/javascripts/ide/index.js31
-rw-r--r--app/assets/javascripts/ide/lib/common/disposable.js14
-rw-r--r--app/assets/javascripts/ide/lib/common/model.js64
-rw-r--r--app/assets/javascripts/ide/lib/common/model_manager.js32
-rw-r--r--app/assets/javascripts/ide/lib/decorations/controller.js43
-rw-r--r--app/assets/javascripts/ide/lib/diff/controller.js71
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff.js30
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff_worker.js10
-rw-r--r--app/assets/javascripts/ide/lib/editor.js110
-rw-r--r--app/assets/javascripts/ide/lib/editor_options.js2
-rw-r--r--app/assets/javascripts/ide/monaco_loader.js16
-rw-r--r--app/assets/javascripts/ide/services/index.js47
-rw-r--r--app/assets/javascripts/ide/stores/actions.js196
-rw-r--r--app/assets/javascripts/ide/stores/actions/branch.js43
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js137
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js27
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js188
-rw-r--r--app/assets/javascripts/ide/stores/getters.js19
-rw-r--r--app/assets/javascripts/ide/stores/index.js15
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js46
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js70
-rw-r--r--app/assets/javascripts/ide/stores/mutations/branch.js28
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js74
-rw-r--r--app/assets/javascripts/ide/stores/mutations/project.js23
-rw-r--r--app/assets/javascripts/ide/stores/mutations/tree.js36
-rw-r--r--app/assets/javascripts/ide/stores/state.js23
-rw-r--r--app/assets/javascripts/ide/stores/utils.js177
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js8
-rw-r--r--app/assets/javascripts/milestone_select.js3
-rw-r--r--app/assets/javascripts/mr_notes/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/environments/terminal/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/show/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/index/index.js19
-rw-r--r--app/assets/javascripts/pages/search/init_filtered_search.js2
-rw-r--r--app/assets/javascripts/pipelines/components/blank_state.vue32
-rw-r--r--app/assets/javascripts/pipelines/components/empty_state.vue49
-rw-r--r--app/assets/javascripts/pipelines/components/error_state.vue26
-rw-r--r--app/assets/javascripts/pipelines/components/nav_controls.vue81
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue250
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js25
-rw-r--r--app/assets/javascripts/terminal/index.js (renamed from app/assets/javascripts/terminal/terminal_bundle.js)2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue149
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue78
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue84
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue34
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue21
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue22
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue27
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue63
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue48
-rw-r--r--app/assets/javascripts/vue_shared/models/label.js (renamed from app/assets/javascripts/boards/models/label.js)4
84 files changed, 915 insertions, 3845 deletions
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 417ac31fc86..81c89441424 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -12,7 +12,7 @@ $(() => {
const $container = $(container);
$container
- .find('.js-toggle-button .fa')
+ .find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down')
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
@@ -22,7 +22,7 @@ $(() => {
}
$('body').on('click', '.js-toggle-button', function toggleButton(e) {
- e.target.classList.toggle('open');
+ e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'open');
toggleContainer($(this).closest('.js-toggle-container'));
const targetTag = e.currentTarget.tagName.toLowerCase();
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index b8749a13f68..efc0da2e7a2 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -5,12 +5,12 @@ import Vue from 'vue';
import Flash from '~/flash';
import { __ } from '~/locale';
+import '~/vue_shared/models/label';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
import './models/issue';
-import './models/label';
import './models/list';
import './models/milestone';
import './models/project';
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index ce19069f103..466a5b5d635 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -20,10 +20,6 @@
type: String,
required: true,
},
- emptyStateSvgPath: {
- type: String,
- required: true,
- },
errorStateSvgPath: {
type: String,
required: true,
@@ -45,23 +41,14 @@
},
computed: {
- /**
- * Empty state is only rendered if after the first request we receive no pipelines.
- *
- * @return {Boolean}
- */
- shouldRenderEmptyState() {
- return !this.state.pipelines.length &&
- !this.isLoading &&
- this.hasMadeRequest &&
- !this.hasError;
- },
-
shouldRenderTable() {
return !this.isLoading &&
this.state.pipelines.length > 0 &&
!this.hasError;
},
+ shouldRenderErrorState() {
+ return this.hasError && !this.isLoading;
+ },
},
created() {
this.service = new PipelinesService(this.endpoint);
@@ -92,25 +79,22 @@
<div class="content-list pipelines">
<loading-icon
- label="Loading pipelines"
+ :label="s__('Pipelines|Loading Pipelines')"
size="3"
v-if="isLoading"
+ class="prepend-top-20"
/>
- <empty-state
- v-if="shouldRenderEmptyState"
- :help-page-path="helpPagePath"
- :empty-state-svg-path="emptyStateSvgPath"
- />
-
- <error-state
- v-if="shouldRenderErrorState"
- :error-state-svg-path="errorStateSvgPath"
+ <svg-blank-state
+ v-else-if="shouldRenderErrorState"
+ :svg-path="errorStateSvgPath"
+ :message="s__(`Pipelines|There was an error fetching the pipelines.
+ Try again in a few moments or contact your support team.`)"
/>
<div
class="table-holder"
- v-if="shouldRenderTable"
+ v-else-if="shouldRenderTable"
>
<pipelines-table-component
:pipelines="state.pipelines"
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index ee49a7be0b2..e6390f0855b 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -16,6 +16,7 @@ export default class FilteredSearchDropdownManager {
page,
isGroup,
isGroupAncestor,
+ isGroupDecendent,
filteredSearchTokenKeys,
}) {
this.container = FilteredSearchContainer.container;
@@ -26,6 +27,7 @@ export default class FilteredSearchDropdownManager {
this.page = page;
this.groupsOnly = isGroup;
this.groupAncestor = isGroupAncestor;
+ this.isGroupDecendent = isGroupDecendent;
this.setupMapping();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index c6970d7837f..71b7e80335b 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -22,11 +22,13 @@ export default class FilteredSearchManager {
page,
isGroup = false,
isGroupAncestor = false,
+ isGroupDecendent = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
}) {
this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor;
+ this.isGroupDecendent = isGroupDecendent;
this.states = ['opened', 'closed', 'merged', 'all'];
this.page = page;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index a19bb882410..600024c21c3 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
-import AjaxCache from '../lib/utils/ajax_cache';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import { objectToQueryString } from '~/lib/utils/common_utils';
import Flash from '../flash';
import FilteredSearchContainer from './container';
import UsersCache from '../lib/utils/users_cache';
@@ -16,6 +17,21 @@ export default class FilteredSearchVisualTokens {
};
}
+ /**
+ * Returns a computed API endpoint
+ * and query string composed of values from endpointQueryParams
+ * @param {String} endpoint
+ * @param {String} endpointQueryParams
+ */
+ static getEndpointWithQueryParams(endpoint, endpointQueryParams) {
+ if (!endpointQueryParams) {
+ return endpoint;
+ }
+
+ const queryString = objectToQueryString(JSON.parse(endpointQueryParams));
+ return `${endpoint}?${queryString}`;
+ }
+
static unselectTokens() {
const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
@@ -86,7 +102,10 @@ export default class FilteredSearchVisualTokens {
static updateLabelTokenColor(tokenValueContainer, tokenValue) {
const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
const baseEndpoint = filteredSearchInput.dataset.baseEndpoint;
- const labelsEndpoint = `${baseEndpoint}/labels.json`;
+ const labelsEndpoint = FilteredSearchVisualTokens.getEndpointWithQueryParams(
+ `${baseEndpoint}/labels.json`,
+ filteredSearchInput.dataset.endpointQueryParams,
+ );
return AjaxCache.retrieve(labelsEndpoint)
.then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index b8f0566f48c..0578f43d5af 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -152,14 +152,14 @@ export default {
showLeaveGroupModal(group, parentGroup) {
this.targetGroup = group;
this.targetParentGroup = parentGroup;
- this.updateModal = true;
+ this.showModal = true;
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
},
hideLeaveGroupModal() {
- this.updateModal = false;
+ this.showModal = false;
},
leaveGroup() {
- this.updateModal = false;
+ this.showModal = false;
this.targetGroup.isBeingRemoved = true;
this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json())
@@ -208,9 +208,9 @@ export default {
:page-info="pageInfo"
/>
<modal
- v-show="showModal"
- :primary-button-label="__('Leave')"
+ v-if="showModal"
kind="warning"
+ :primary-button-label="__('Leave')"
:title="__('Are you sure?')"
:text="groupLeaveConfirmationMessage"
@cancel="hideLeaveGroupModal"
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
deleted file mode 100644
index a8459b011df..00000000000
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script>
- import { mapState } from 'vuex';
- import icon from '../../../vue_shared/components/icon.vue';
- import listItem from './list_item.vue';
- import listCollapsed from './list_collapsed.vue';
-
- export default {
- components: {
- icon,
- listItem,
- listCollapsed,
- },
- props: {
- title: {
- type: String,
- required: true,
- },
- fileList: {
- type: Array,
- required: true,
- },
- },
- computed: {
- ...mapState([
- 'currentProjectId',
- 'currentBranchId',
- 'rightPanelCollapsed',
- ]),
- },
- methods: {
- toggleCollapsed() {
- this.$emit('toggleCollapsed');
- },
- },
- };
-</script>
-
-<template>
- <div class="multi-file-commit-list">
- <list-collapsed
- v-if="rightPanelCollapsed"
- />
- <template v-else>
- <ul
- v-if="fileList.length"
- class="list-unstyled append-bottom-0"
- >
- <li
- v-for="file in fileList"
- :key="file.key"
- >
- <list-item
- :file="file"
- />
- </li>
- </ul>
- <div
- v-else
- class="help-block prepend-top-0"
- >
- No changes
- </div>
- </template>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
deleted file mode 100644
index 6a0262f271b..00000000000
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-<script>
- import { mapGetters } from 'vuex';
- import icon from '../../../vue_shared/components/icon.vue';
-
- export default {
- components: {
- icon,
- },
- computed: {
- ...mapGetters([
- 'addedFiles',
- 'modifiedFiles',
- ]),
- },
- };
-</script>
-
-<template>
- <div
- class="multi-file-commit-list-collapsed text-center"
- >
- <icon
- name="file-addition"
- :size="18"
- css-classes="multi-file-addition append-bottom-10"
- />
- {{ addedFiles.length }}
- <icon
- name="file-modified"
- :size="18"
- css-classes="multi-file-modified prepend-top-10 append-bottom-10"
- />
- {{ modifiedFiles.length }}
- </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
deleted file mode 100644
index 742f746e02f..00000000000
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<script>
- import icon from '../../../vue_shared/components/icon.vue';
-
- export default {
- components: {
- icon,
- },
- props: {
- file: {
- type: Object,
- required: true,
- },
- },
- computed: {
- iconName() {
- return this.file.tempFile ? 'file-addition' : 'file-modified';
- },
- iconClass() {
- return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
- },
- },
- };
-</script>
-
-<template>
- <div class="multi-file-commit-list-item">
- <icon
- :name="iconName"
- :size="16"
- :css-classes="iconClass"
- />
- <span class="multi-file-commit-list-path">
- {{ file.path }}
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
deleted file mode 100644
index 89981ab2c65..00000000000
--- a/app/assets/javascripts/ide/components/ide.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<script>
- import { mapState, mapGetters } from 'vuex';
- import ideSidebar from './ide_side_bar.vue';
- import ideContextbar from './ide_context_bar.vue';
- import repoTabs from './repo_tabs.vue';
- import repoFileButtons from './repo_file_buttons.vue';
- import ideStatusBar from './ide_status_bar.vue';
- import repoPreview from './repo_preview.vue';
- import repoEditor from './repo_editor.vue';
-
- export default {
- components: {
- ideSidebar,
- ideContextbar,
- repoTabs,
- repoFileButtons,
- ideStatusBar,
- repoEditor,
- repoPreview,
- },
- props: {
- emptyStateSvgPath: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapState([
- 'currentBlobView',
- 'selectedFile',
- ]),
- ...mapGetters([
- 'changedFiles',
- 'activeFile',
- ]),
- },
- mounted() {
- const returnValue = 'Are you sure you want to lose unsaved changes?';
- window.onbeforeunload = (e) => {
- if (!this.changedFiles.length) return undefined;
-
- Object.assign(e, {
- returnValue,
- });
- return returnValue;
- };
- },
- };
-</script>
-
-<template>
- <div
- class="ide-view"
- >
- <ide-sidebar />
- <div
- class="multi-file-edit-pane"
- >
- <template
- v-if="activeFile"
- >
- <repo-tabs/>
- <component
- class="multi-file-edit-pane-content"
- :is="currentBlobView"
- />
- <repo-file-buttons />
- <ide-status-bar
- :file="selectedFile"
- />
- </template>
- <template
- v-else
- >
- <div class="ide-empty-state">
- <div class="row js-empty-state">
- <div class="col-xs-12">
- <div class="svg-content svg-250">
- <img :src="emptyStateSvgPath" />
- </div>
- </div>
- <div class="col-xs-12">
- <div class="text-content text-center">
- <h4>
- Welcome to the GitLab IDE
- </h4>
- <p>
- You can select a file in the left sidebar to begin
- editing and use the right sidebar to commit your changes.
- </p>
- </div>
- </div>
- </div>
- </div>
- </template>
- </div>
- <ide-contextbar/>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue
deleted file mode 100644
index 9d933b8891d..00000000000
--- a/app/assets/javascripts/ide/components/ide_context_bar.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-<script>
- import { mapGetters, mapState, mapActions } from 'vuex';
- import icon from '~/vue_shared/components/icon.vue';
- import panelResizer from '~/vue_shared/components/panel_resizer.vue';
- import repoCommitSection from './repo_commit_section.vue';
-
- export default {
- components: {
- repoCommitSection,
- icon,
- panelResizer,
- },
- data() {
- return {
- width: 290,
- };
- },
- computed: {
- ...mapState([
- 'rightPanelCollapsed',
- ]),
- ...mapGetters([
- 'changedFiles',
- ]),
- currentIcon() {
- return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
- },
- maxSize() {
- return window.innerWidth / 2;
- },
- panelStyle() {
- if (!this.rightPanelCollapsed) {
- return { width: `${this.width}px` };
- }
- return {};
- },
- },
- methods: {
- ...mapActions([
- 'setPanelCollapsedStatus',
- 'setResizingStatus',
- ]),
- toggleCollapsed() {
- this.setPanelCollapsedStatus({
- side: 'right',
- collapsed: !this.rightPanelCollapsed,
- });
- },
- resizingStarted() {
- this.setResizingStatus(true);
- },
- resizingEnded() {
- this.setResizingStatus(false);
- },
- },
- };
-</script>
-
-<template>
- <div
- class="multi-file-commit-panel"
- :class="{
- 'is-collapsed': rightPanelCollapsed,
- }"
- :style="panelStyle"
- >
- <div class="multi-file-commit-panel-section">
- <header
- class="multi-file-commit-panel-header"
- :class="{
- 'is-collapsed': rightPanelCollapsed,
- }"
- >
- <div
- class="multi-file-commit-panel-header-title"
- v-if="!rightPanelCollapsed"
- >
- <icon
- name="list-bulleted"
- :size="18"
- />
- Staged
- </div>
- <button
- type="button"
- class="btn btn-transparent multi-file-commit-panel-collapse-btn"
- @click="toggleCollapsed"
- >
- <icon
- :name="currentIcon"
- :size="18"
- />
- </button>
- </header>
- <repo-commit-section />
- </div>
- <panel-resizer
- :size.sync="width"
- :enabled="!rightPanelCollapsed"
- :start-size="290"
- :min-size="200"
- :max-size="maxSize"
- @resize-start="resizingStarted"
- @resize-end="resizingEnded"
- side="left"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
deleted file mode 100644
index 2fbff2bd789..00000000000
--- a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<script>
-import icon from '~/vue_shared/components/icon.vue';
-import repoTree from './ide_repo_tree.vue';
-import newDropdown from './new_dropdown/index.vue';
-
-export default {
- components: {
- repoTree,
- icon,
- newDropdown,
- },
- props: {
- projectId: {
- type: String,
- required: true,
- },
- branch: {
- type: Object,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div class="branch-container">
- <div class="branch-header">
- <div class="branch-header-title">
- <icon
- name="branch"
- :size="12"
- />
- {{ branch.name }}
- </div>
- <div class="branch-header-btns">
- <new-dropdown
- :project-id="projectId"
- :branch="branch.name"
- path=""
- />
- </div>
- </div>
- <div>
- <repo-tree :tree-id="branch.treeId" />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue
deleted file mode 100644
index 32bf7175c88..00000000000
--- a/app/assets/javascripts/ide/components/ide_project_tree.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<script>
-import projectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
-import branchesTree from './ide_project_branches_tree.vue';
-
-export default {
- components: {
- branchesTree,
- projectAvatarImage,
- },
- props: {
- project: {
- type: Object,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div class="projects-sidebar">
- <div class="context-header">
- <a
- :title="project.name"
- :href="project.web_url"
- >
- <div class="avatar-container s40 project-avatar">
- <project-avatar-image
- class="avatar-container project-avatar"
- :link-href="project.path"
- :img-src="project.avatar_url"
- :img-alt="project.name"
- :img-size="40"
- />
- </div>
- <div class="sidebar-context-title">
- {{ project.name }}
- </div>
- </a>
- </div>
- <div class="multi-file-commit-panel-inner-scroll">
- <branches-tree
- v-for="branch in project.branches"
- :key="branch.name"
- :project-id="project.path_with_namespace"
- :branch="branch"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue
deleted file mode 100644
index 4a324264992..00000000000
--- a/app/assets/javascripts/ide/components/ide_repo_tree.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<script>
-import { mapState } from 'vuex';
-import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-import repoPreviousDirectory from './repo_prev_directory.vue';
-import repoFile from './repo_file.vue';
-import { treeList } from '../stores/utils';
-
-export default {
- components: {
- repoPreviousDirectory,
- repoFile,
- skeletonLoadingContainer,
- },
- props: {
- treeId: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapState([
- 'trees',
- 'isRoot',
- ]),
- ...mapState({
- projectName(state) {
- return state.project.name;
- },
- }),
- fetchedList() {
- return treeList(this.$store.state, this.treeId);
- },
- hasPreviousDirectory() {
- return !this.isRoot && this.fetchedList.length;
- },
- showLoading() {
- if (this.trees[this.treeId]) {
- return this.trees[this.treeId].loading;
- }
- return true;
- },
- },
-};
-</script>
-
-<template>
- <div>
- <div class="ide-file-list">
- <table class="table">
- <tbody
- v-if="treeId"
- >
- <repo-previous-directory
- v-if="hasPreviousDirectory"
- />
- <template v-if="showLoading">
- <div
- class="multi-file-loading-container"
- v-for="n in 3"
- :key="n"
- >
- <skeleton-loading-container />
- </div>
- </template>
- <repo-file
- v-for="file in fetchedList"
- :key="file.key"
- :file="file"
- />
- </tbody>
- </table>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
deleted file mode 100644
index 18b5059a17f..00000000000
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ /dev/null
@@ -1,114 +0,0 @@
-<script>
- import { mapState, mapActions } from 'vuex';
- import icon from '~/vue_shared/components/icon.vue';
- import panelResizer from '~/vue_shared/components/panel_resizer.vue';
- import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
- import projectTree from './ide_project_tree.vue';
-
- export default {
- components: {
- projectTree,
- icon,
- panelResizer,
- skeletonLoadingContainer,
- },
- data() {
- return {
- width: 290,
- };
- },
- computed: {
- ...mapState([
- 'loading',
- 'projects',
- 'leftPanelCollapsed',
- ]),
- currentIcon() {
- return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
- },
- maxSize() {
- return window.innerWidth / 2;
- },
- panelStyle() {
- if (!this.leftPanelCollapsed) {
- return { width: `${this.width}px` };
- }
- return {};
- },
- showLoading() {
- return this.loading;
- },
- },
- methods: {
- ...mapActions([
- 'setPanelCollapsedStatus',
- 'setResizingStatus',
- ]),
- toggleCollapsed() {
- this.setPanelCollapsedStatus({
- side: 'left',
- collapsed: !this.leftPanelCollapsed,
- });
- },
- resizingStarted() {
- this.setResizingStatus(true);
- },
- resizingEnded() {
- this.setResizingStatus(false);
- },
- },
- };
-</script>
-
-<template>
- <div
- class="multi-file-commit-panel"
- :class="{
- 'is-collapsed': leftPanelCollapsed,
- }"
- :style="panelStyle"
- >
- <div class="multi-file-commit-panel-inner">
- <template v-if="showLoading">
- <div
- class="multi-file-loading-container"
- v-for="n in 3"
- :key="n"
- >
- <skeleton-loading-container />
- </div>
- </template>
- <project-tree
- v-for="project in projects"
- :key="project.id"
- :project="project"
- />
- </div>
- <button
- type="button"
- class="btn btn-transparent left-collapse-btn"
- @click="toggleCollapsed"
- >
- <icon
- :name="currentIcon"
- :size="18"
- />
- <span
- v-if="!leftPanelCollapsed"
- class="collapse-text"
- >
- Collapse sidebar
- </span>
- </button>
- <panel-resizer
- :size.sync="width"
- :enabled="!leftPanelCollapsed"
- :start-size="290"
- :min-size="200"
- :max-size="maxSize"
- @resize-start="resizingStarted"
- @resize-end="resizingEnded"
- side="right"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
deleted file mode 100644
index 97ae64b206d..00000000000
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<script>
- import { mapState } from 'vuex';
- import icon from '~/vue_shared/components/icon.vue';
- import tooltip from '~/vue_shared/directives/tooltip';
- import timeAgoMixin from '~/vue_shared/mixins/timeago';
-
- export default {
- components: {
- icon,
- },
- directives: {
- tooltip,
- },
- mixins: [
- timeAgoMixin,
- ],
- props: {
- file: {
- type: Object,
- required: true,
- },
- },
- computed: {
- ...mapState([
- 'selectedFile',
- ]),
- },
- };
-</script>
-
-<template>
- <div class="ide-status-bar">
- <div>
- <icon
- name="branch"
- :size="12"
- />
- {{ selectedFile.branchId }}
- </div>
- <div>
- <div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
- Last commit:
- <a
- v-tooltip
- :title="selectedFile.lastCommit.message"
- :href="selectedFile.lastCommit.url"
- >
- {{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
- {{ selectedFile.lastCommit.author }}
- </a>
- </div>
- </div>
- <div class="text-right">
- {{ selectedFile.name }}
- </div>
- <div class="text-right">
- {{ selectedFile.eol }}
- </div>
- <div class="text-right">
- {{ file.editorRow }}:{{ file.editorColumn }}
- </div>
- <div class="text-right">
- {{ selectedFile.fileLanguage }}
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue
deleted file mode 100644
index 1e8d5bb6453..00000000000
--- a/app/assets/javascripts/ide/components/new_branch_form.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-<script>
- import { mapState, mapActions } from 'vuex';
- import flash, { hideFlash } from '~/flash';
- import loadingIcon from '~/vue_shared/components/loading_icon.vue';
-
- export default {
- components: {
- loadingIcon,
- },
- data() {
- return {
- branchName: '',
- loading: false,
- };
- },
- computed: {
- ...mapState([
- 'currentBranch',
- ]),
- btnDisabled() {
- return this.loading || this.branchName === '';
- },
- },
- created() {
- // Dropdown is outside of Vue instance & is controlled by Bootstrap
- this.$dropdown = $('.git-revision-dropdown');
-
- // text element is outside Vue app
- this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
- },
- methods: {
- ...mapActions([
- 'createNewBranch',
- ]),
- toggleDropdown() {
- this.$dropdown.dropdown('toggle');
- },
- submitNewBranch() {
- // need to query as the element is appended outside of Vue
- const flashEl = this.$refs.flashContainer.querySelector('.flash-alert');
-
- this.loading = true;
-
- if (flashEl) {
- hideFlash(flashEl, false);
- }
-
- this.createNewBranch(this.branchName)
- .then(() => {
- this.loading = false;
- this.branchName = '';
-
- if (this.dropdownText) {
- this.dropdownText.textContent = this.currentBranchId;
- }
-
- this.toggleDropdown();
- })
- .catch(res => res.json().then((data) => {
- this.loading = false;
- flash(data.message, 'alert', this.$el);
- }));
- },
- },
- };
-</script>
-
-<template>
- <div>
- <div
- class="flash-container"
- ref="flashContainer"
- >
- </div>
- <p>
- Create from:
- <code>{{ currentBranch }}</code>
- </p>
- <input
- class="form-control js-new-branch-name"
- type="text"
- placeholder="Name new branch"
- v-model="branchName"
- @keyup.enter.stop.prevent="submitNewBranch"
- />
- <div class="prepend-top-default clearfix">
- <button
- type="button"
- class="btn btn-primary pull-left"
- :disabled="btnDisabled"
- @click.stop.prevent="submitNewBranch"
- >
- <loading-icon
- v-if="loading"
- :inline="true"
- />
- <span>Create</span>
- </button>
- <button
- type="button"
- class="btn btn-default pull-right"
- @click.stop.prevent="toggleDropdown"
- >
- Cancel
- </button>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
deleted file mode 100644
index ef653357f5f..00000000000
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<script>
- import newModal from './modal.vue';
- import upload from './upload.vue';
- import icon from '../../../vue_shared/components/icon.vue';
-
- export default {
- components: {
- icon,
- newModal,
- upload,
- },
- props: {
- branch: {
- type: String,
- required: true,
- },
- path: {
- type: String,
- required: true,
- },
- parent: {
- type: Object,
- default: null,
- },
- },
- data() {
- return {
- openModal: false,
- modalType: '',
- };
- },
- methods: {
- createNewItem(type) {
- this.modalType = type;
- this.openModal = true;
- },
- hideModal() {
- this.openModal = false;
- },
- },
- };
-</script>
-
-<template>
- <div class="repo-new-btn pull-right">
- <div class="dropdown">
- <button
- type="button"
- class="btn btn-sm btn-default dropdown-toggle add-to-tree"
- data-toggle="dropdown"
- aria-label="Create new file or directory"
- >
- <icon
- name="plus"
- :size="12"
- css-classes="pull-left"
- />
- <icon
- name="arrow-down"
- :size="12"
- css-classes="pull-left"
- />
- </button>
- <ul class="dropdown-menu dropdown-menu-right">
- <li>
- <a
- href="#"
- role="button"
- @click.prevent="createNewItem('blob')"
- >
- {{ __('New file') }}
- </a>
- </li>
- <li>
- <upload
- :branch-id="branch"
- :path="path"
- :parent="parent"
- />
- </li>
- <li>
- <a
- href="#"
- role="button"
- @click.prevent="createNewItem('tree')"
- >
- {{ __('New directory') }}
- </a>
- </li>
- </ul>
- </div>
- <new-modal
- v-if="openModal"
- :type="modalType"
- :branch-id="branch"
- :path="path"
- :parent="parent"
- @hide="hideModal"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
deleted file mode 100644
index 36cd825c6dd..00000000000
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<script>
- import { mapActions, mapState } from 'vuex';
- import { __ } from '../../../locale';
- import modal from '../../../vue_shared/components/modal.vue';
-
- export default {
- components: {
- modal,
- },
- props: {
- branchId: {
- type: String,
- required: true,
- },
- parent: {
- type: Object,
- default: null,
- },
- type: {
- type: String,
- required: true,
- },
- path: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- entryName: this.path !== '' ? `${this.path}/` : '',
- };
- },
- computed: {
- ...mapState([
- 'currentProjectId',
- ]),
- modalTitle() {
- if (this.type === 'tree') {
- return __('Create new directory');
- }
-
- return __('Create new file');
- },
- buttonLabel() {
- if (this.type === 'tree') {
- return __('Create directory');
- }
-
- return __('Create file');
- },
- formLabelName() {
- if (this.type === 'tree') {
- return __('Directory name');
- }
-
- return __('File name');
- },
- },
- mounted() {
- this.$refs.fieldName.focus();
- },
- methods: {
- ...mapActions([
- 'createTempEntry',
- ]),
- createEntryInStore() {
- this.createTempEntry({
- projectId: this.currentProjectId,
- branchId: this.branchId,
- parent: this.parent,
- name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
- type: this.type,
- });
-
- this.hideModal();
- },
- hideModal() {
- this.$emit('hide');
- },
- },
- };
-</script>
-
-<template>
- <modal
- :title="modalTitle"
- :primary-button-label="buttonLabel"
- kind="success"
- @cancel="hideModal"
- @submit="createEntryInStore"
- >
- <form
- class="form-horizontal"
- slot="body"
- @submit.prevent="createEntryInStore"
- >
- <fieldset class="form-group append-bottom-0">
- <label class="label-light col-sm-3">
- {{ formLabelName }}
- </label>
- <div class="col-sm-9">
- <input
- type="text"
- class="form-control"
- v-model="entryName"
- ref="fieldName"
- />
- </div>
- </fieldset>
- </form>
- </modal>
-</template>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
deleted file mode 100644
index 6244737fa43..00000000000
--- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-<script>
- import { mapActions, mapState } from 'vuex';
-
- export default {
- props: {
- branchId: {
- type: String,
- required: true,
- },
- parent: {
- type: Object,
- default: null,
- },
- },
- computed: {
- ...mapState([
- 'trees',
- 'currentProjectId',
- ]),
- },
- mounted() {
- this.$refs.fileUpload.addEventListener('change', this.openFile);
- },
- beforeDestroy() {
- this.$refs.fileUpload.removeEventListener('change', this.openFile);
- },
- methods: {
- ...mapActions([
- 'createTempEntry',
- ]),
- createFile(target, file, isText) {
- const { name } = file;
- let { result } = target;
-
- if (!isText) {
- result = result.split('base64,')[1];
- }
-
- this.createTempEntry({
- name,
- projectId: this.currentProjectId,
- branchId: this.branchId,
- parent: this.parent,
- type: 'blob',
- content: result,
- base64: !isText,
- });
- },
- readFile(file) {
- const reader = new FileReader();
- const isText = file.type.match(/text.*/) !== null;
-
- reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
-
- if (isText) {
- reader.readAsText(file);
- } else {
- reader.readAsDataURL(file);
- }
- },
- openFile() {
- Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
- },
- startFileUpload() {
- this.$refs.fileUpload.click();
- },
- },
- };
-</script>
-
-<template>
- <div>
- <a
- href="#"
- role="button"
- @click.prevent="startFileUpload"
- >
- {{ __('Upload file') }}
- </a>
- <input
- id="file-upload"
- type="file"
- class="hidden"
- ref="fileUpload"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
deleted file mode 100644
index 37f2cf30a29..00000000000
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ /dev/null
@@ -1,171 +0,0 @@
-<script>
-import { mapGetters, mapState, mapActions } from 'vuex';
-import tooltip from '~/vue_shared/directives/tooltip';
-import icon from '~/vue_shared/components/icon.vue';
-import modal from '~/vue_shared/components/modal.vue';
-import commitFilesList from './commit_sidebar/list.vue';
-
-export default {
- components: {
- modal,
- icon,
- commitFilesList,
- },
- directives: {
- tooltip,
- },
- data() {
- return {
- showNewBranchModal: false,
- submitCommitsLoading: false,
- startNewMR: false,
- commitMessage: '',
- };
- },
- computed: {
- ...mapState([
- 'currentProjectId',
- 'currentBranchId',
- 'rightPanelCollapsed',
- ]),
- ...mapGetters([
- 'changedFiles',
- ]),
- commitButtonDisabled() {
- return this.commitMessage === '' || this.submitCommitsLoading || !this.changedFiles.length;
- },
- commitMessageCount() {
- return this.commitMessage.length;
- },
- },
- methods: {
- ...mapActions([
- 'checkCommitStatus',
- 'commitChanges',
- 'getTreeData',
- 'setPanelCollapsedStatus',
- ]),
- makeCommit(newBranch = false) {
- const createNewBranch = newBranch || this.startNewMR;
-
- const payload = {
- branch: createNewBranch ?
- `${this.currentBranchId}-${new Date().getTime().toString()}` :
- this.currentBranchId,
- commit_message: this.commitMessage,
- actions: this.changedFiles.map(f => ({
- action: f.tempFile ? 'create' : 'update',
- file_path: f.path,
- content: f.content,
- encoding: f.base64 ? 'base64' : 'text',
- })),
- start_branch: createNewBranch ? this.currentBranchId : undefined,
- };
-
- this.showNewBranchModal = false;
- this.submitCommitsLoading = true;
-
- this.commitChanges({ payload, newMr: this.startNewMR })
- .then(() => {
- this.submitCommitsLoading = false;
- this.commitMessage = '';
- this.startNewMR = false;
- })
- .catch(() => {
- this.submitCommitsLoading = false;
- });
- },
- tryCommit() {
- this.submitCommitsLoading = true;
-
- this.checkCommitStatus()
- .then((branchChanged) => {
- if (branchChanged) {
- this.showNewBranchModal = true;
- } else {
- this.makeCommit();
- }
- })
- .catch(() => {
- this.submitCommitsLoading = false;
- });
- },
- toggleCollapsed() {
- this.setPanelCollapsedStatus({
- side: 'right',
- collapsed: !this.rightPanelCollapsed,
- });
- },
- },
-};
-</script>
-
-<template>
- <div class="multi-file-commit-panel-section">
- <modal
- v-if="showNewBranchModal"
- :primary-button-label="__('Create new branch')"
- kind="primary"
- :title="__('Branch has changed')"
- :text="__(`This branch has changed since
-you started editing. Would you like to create a new branch?`)"
- @cancel="showNewBranchModal = false"
- @submit="makeCommit(true)"
- />
- <commit-files-list
- title="Staged"
- :file-list="changedFiles"
- :collapsed="rightPanelCollapsed"
- @toggleCollapsed="toggleCollapsed"
- />
- <form
- class="form-horizontal multi-file-commit-form"
- @submit.prevent="tryCommit"
- v-if="!rightPanelCollapsed"
- >
- <div class="multi-file-commit-fieldset">
- <textarea
- class="form-control multi-file-commit-message"
- name="commit-message"
- v-model="commitMessage"
- placeholder="Commit message"
- >
- </textarea>
- </div>
- <div class="multi-file-commit-fieldset">
- <label
- v-tooltip
- title="Create a new merge request with these changes"
- data-container="body"
- data-placement="top"
- >
- <input
- type="checkbox"
- v-model="startNewMR"
- />
- Merge Request
- </label>
- <button
- type="submit"
- :disabled="commitButtonDisabled"
- class="btn btn-default btn-sm append-right-10 prepend-left-10"
- :class="{ disabled: submitCommitsLoading }"
- >
- <i
- v-if="submitCommitsLoading"
- class="js-commit-loading-icon fa fa-spinner fa-spin"
- aria-hidden="true"
- aria-label="loading"
- >
- </i>
- Commit
- </button>
- <div
- class="multi-file-commit-message-count"
- >
- {{ commitMessageCount }}
- </div>
- </div>
- </form>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue
deleted file mode 100644
index fe4320731d9..00000000000
--- a/app/assets/javascripts/ide/components/repo_edit_button.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<script>
-import { mapGetters, mapActions, mapState } from 'vuex';
-import modal from '~/vue_shared/components/modal.vue';
-
-export default {
- components: {
- modal,
- },
- computed: {
- ...mapState([
- 'editMode',
- 'discardPopupOpen',
- ]),
- ...mapGetters([
- 'canEditFile',
- ]),
- buttonLabel() {
- return this.editMode ? this.__('Cancel edit') : this.__('Edit');
- },
- },
- methods: {
- ...mapActions([
- 'toggleEditMode',
- 'closeDiscardPopup',
- ]),
- },
-};
-</script>
-
-<template>
- <div class="editable-mode">
- <button
- v-if="canEditFile"
- class="btn btn-default"
- type="button"
- @click.prevent="toggleEditMode()">
- <i
- v-if="!editMode"
- class="fa fa-pencil"
- aria-hidden="true">
- </i>
- <span>
- {{ buttonLabel }}
- </span>
- </button>
- <modal
- v-if="discardPopupOpen"
- class="text-left"
- :primary-button-label="__('Discard changes')"
- kind="warning"
- :title="__('Are you sure?')"
- :text="__('Are you sure you want to discard your changes?')"
- @cancel="closeDiscardPopup"
- @submit="toggleEditMode(true)"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
deleted file mode 100644
index f31cc12339b..00000000000
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-<script>
-/* global monaco */
-import { mapState, mapGetters, mapActions } from 'vuex';
-import flash from '~/flash';
-import monacoLoader from '../monaco_loader';
-import Editor from '../lib/editor';
-
-export default {
- computed: {
- ...mapGetters([
- 'activeFile',
- 'activeFileExtension',
- ]),
- ...mapState([
- 'leftPanelCollapsed',
- 'rightPanelCollapsed',
- 'panelResizing',
- ]),
- shouldHideEditor() {
- return this.activeFile.binary && !this.activeFile.raw;
- },
- },
- watch: {
- activeFile(oldVal, newVal) {
- if (newVal && !newVal.active) {
- this.initMonaco();
- }
- },
- leftPanelCollapsed() {
- this.editor.updateDimensions();
- },
- rightPanelCollapsed() {
- this.editor.updateDimensions();
- },
- panelResizing(isResizing) {
- if (isResizing === false) {
- this.editor.updateDimensions();
- }
- },
- },
- beforeDestroy() {
- this.editor.dispose();
- },
- mounted() {
- if (this.editor && monaco) {
- this.initMonaco();
- } else {
- monacoLoader(['vs/editor/editor.main'], () => {
- this.editor = Editor.create(monaco);
-
- this.initMonaco();
- });
- }
- },
- methods: {
- ...mapActions([
- 'getRawFileData',
- 'changeFileContent',
- 'setFileLanguage',
- 'setEditorPosition',
- 'setFileEOL',
- ]),
- initMonaco() {
- if (this.shouldHideEditor) return;
-
- this.editor.clearEditor();
-
- this.getRawFileData(this.activeFile)
- .then(() => {
- this.editor.createInstance(this.$refs.editor);
- })
- .then(() => this.setupEditor())
- .catch((err) => {
- flash('Error setting up monaco. Please try again.', 'alert', document, null, false, true);
- throw err;
- });
- },
- setupEditor() {
- if (!this.activeFile) return;
-
- const model = this.editor.createModel(this.activeFile);
-
- this.editor.attachModel(model);
-
- model.onChange((m) => {
- this.changeFileContent({
- file: this.activeFile,
- content: m.getValue(),
- });
- });
-
- // Handle Cursor Position
- this.editor.onPositionChange((instance, e) => {
- this.setEditorPosition({
- editorRow: e.position.lineNumber,
- editorColumn: e.position.column,
- });
- });
-
- this.editor.setPosition({
- lineNumber: this.activeFile.editorRow,
- column: this.activeFile.editorColumn,
- });
-
- // Handle File Language
- this.setFileLanguage({
- fileLanguage: model.language,
- });
-
- // Get File eol
- this.setFileEOL({
- eol: model.eol,
- });
- },
- },
-};
-</script>
-
-<template>
- <div
- id="ide"
- class="blob-viewer-container blob-editor-container"
- >
- <div
- v-if="shouldHideEditor"
- v-html="activeFile.html"
- >
- </div>
- <div
- v-show="!shouldHideEditor"
- ref="editor"
- class="multi-file-editor-holder"
- >
- </div>
- </div>
-</template>
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 cbbab765e1c..00000000000
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ /dev/null
@@ -1,165 +0,0 @@
-<script>
- import { mapState } from 'vuex';
- import timeAgoMixin from '~/vue_shared/mixins/timeago';
- import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
- import fileIcon from '~/vue_shared/components/file_icon.vue';
- import newDropdown from './new_dropdown/index.vue';
-
- export default {
- components: {
- skeletonLoadingContainer,
- newDropdown,
- fileIcon,
- },
- mixins: [
- timeAgoMixin,
- ],
- props: {
- file: {
- type: Object,
- required: true,
- },
- showExtraColumns: {
- type: Boolean,
- default: false,
- },
- },
- computed: {
- ...mapState([
- 'leftPanelCollapsed',
- ]),
- isSubmodule() {
- return this.file.type === 'submodule';
- },
- isTree() {
- return this.file.type === 'tree';
- },
- levelIndentation() {
- if (this.file.level > 0) {
- return {
- marginLeft: `${this.file.level * 16}px`,
- };
- }
- return {};
- },
- shortId() {
- return this.file.id.substr(0, 8);
- },
- submoduleColSpan() {
- return !this.leftPanelCollapsed && this.isSubmodule ? 3 : 1;
- },
- fileClass() {
- if (this.file.type === 'blob') {
- if (this.file.active) {
- return 'file-open file-active';
- }
- return this.file.opened ? 'file-open' : '';
- }
- return '';
- },
- changedClass() {
- return {
- 'fa-circle unsaved-icon': this.file.changed || this.file.tempFile,
- };
- },
- },
- updated() {
- if (this.file.type === 'blob' && this.file.active) {
- this.$el.scrollIntoView();
- }
- },
- methods: {
- clickFile(row) {
- // Manual Action if a tree is selected/opened
- if (this.file.type === 'tree' && this.$router.currentRoute.path === `/project${row.url}`) {
- this.$store.dispatch('toggleTreeOpen', {
- endpoint: this.file.url,
- tree: this.file,
- });
- }
- this.$router.push(`/project${row.url}`);
- },
- },
- };
-</script>
-
-<template>
- <tr
- class="file"
- :class="fileClass"
- @click="clickFile(file)">
- <td
- class="multi-file-table-name"
- :colspan="submoduleColSpan"
- >
- <a
- class="repo-file-name"
- >
- <file-icon
- :file-name="file.name"
- :loading="file.loading"
- :folder="file.type === 'tree'"
- :opened="file.opened"
- :style="levelIndentation"
- :size="16"
- />
- {{ file.name }}
- </a>
- <new-dropdown
- v-if="isTree"
- :project-id="file.projectId"
- :branch="file.branchId"
- :path="file.path"
- :parent="file"
- />
- <i
- class="fa"
- v-if="file.changed || file.tempFile"
- :class="changedClass"
- aria-hidden="true"
- >
- </i>
- <template v-if="isSubmodule && file.id">
- @
- <span class="commit-sha">
- <a
- @click.stop
- :href="file.tree_url"
- >
- {{ shortId }}
- </a>
- </span>
- </template>
- </td>
-
- <template v-if="showExtraColumns && !isSubmodule">
- <td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
- <a
- v-if="file.lastCommit.message"
- @click.stop
- :href="file.lastCommit.url"
- >
- {{ file.lastCommit.message }}
- </a>
- <skeleton-loading-container
- v-else
- :small="true"
- />
- </td>
-
- <td class="commit-update hidden-xs text-right">
- <span
- v-if="file.lastCommit.updatedAt"
- :title="tooltipTitle(file.lastCommit.updatedAt)"
- >
- {{ timeFormated(file.lastCommit.updatedAt) }}
- </span>
- <skeleton-loading-container
- v-else
- class="animation-container-right"
- :small="true"
- />
- </td>
- </template>
- </tr>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue
deleted file mode 100644
index aabc0d8eada..00000000000
--- a/app/assets/javascripts/ide/components/repo_file_buttons.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-<script>
-import { mapGetters } from 'vuex';
-
-export default {
- computed: {
- ...mapGetters([
- 'activeFile',
- ]),
- showButtons() {
- return this.activeFile.rawPath ||
- this.activeFile.blamePath ||
- this.activeFile.commitsPath ||
- this.activeFile.permalink;
- },
- rawDownloadButtonLabel() {
- return this.activeFile.binary ? 'Download' : 'Raw';
- },
- },
-};
-</script>
-
-<template>
- <div
- v-if="showButtons"
- class="multi-file-editor-btn-group"
- >
- <a
- :href="activeFile.rawPath"
- target="_blank"
- class="btn btn-default btn-sm raw"
- rel="noopener noreferrer">
- {{ rawDownloadButtonLabel }}
- </a>
-
- <div
- class="btn-group"
- role="group"
- aria-label="File actions"
- >
- <a
- :href="activeFile.blamePath"
- class="btn btn-default btn-sm blame"
- >
- Blame
- </a>
- <a
- :href="activeFile.commitsPath"
- class="btn btn-default btn-sm history"
- >
- History
- </a>
- <a
- :href="activeFile.permalink"
- class="btn btn-default btn-sm permalink"
- >
- Permalink
- </a>
- </div>
- </div>
-</template>
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 79af8c0b0c7..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="hidden-sm hidden-xs">
- <skeleton-loading-container
- :small="true"
- />
- </td>
-
- <td class="hidden-xs">
- <skeleton-loading-container
- class="animation-container-right"
- :small="true"
- />
- </td>
- </template>
- </tr>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_prev_directory.vue b/app/assets/javascripts/ide/components/repo_prev_directory.vue
deleted file mode 100644
index 7cd359ea4ed..00000000000
--- a/app/assets/javascripts/ide/components/repo_prev_directory.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<script>
- import { mapState, mapActions } from 'vuex';
-
- export default {
- computed: {
- ...mapState([
- 'parentTreeUrl',
- 'leftPanelCollapsed',
- ]),
- colSpanCondition() {
- return this.leftPanelCollapsed ? undefined : 3;
- },
- },
- methods: {
- ...mapActions([
- 'getTreeData',
- ]),
- },
- };
-</script>
-
-<template>
- <tr class="file prev-directory">
- <td
- :colspan="colSpanCondition"
- class="table-cell"
- @click.prevent="getTreeData({ endpoint: parentTreeUrl })"
- >
- <a :href="parentTreeUrl">...</a>
- </td>
- </tr>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue
deleted file mode 100644
index a216269e292..00000000000
--- a/app/assets/javascripts/ide/components/repo_preview.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<script>
- import { mapGetters } from 'vuex';
- import LineHighlighter from '~/line_highlighter';
- import syntaxHighlight from '~/syntax_highlight';
-
- export default {
- computed: {
- ...mapGetters([
- 'activeFile',
- ]),
- renderErrorTooLarge() {
- return this.activeFile.renderError === 'too_large';
- },
- },
- mounted() {
- this.highlightFile();
- this.lineHighlighter = new LineHighlighter({
- fileHolderSelector: '.blob-viewer-container',
- scrollFileHolder: true,
- });
- },
- updated() {
- this.$nextTick(() => {
- this.highlightFile();
- });
- },
- methods: {
- highlightFile() {
- syntaxHighlight($(this.$el).find('.file-content'));
- },
- },
- };
-</script>
-
-<template>
- <div>
- <div
- v-if="!activeFile.renderError"
- v-html="activeFile.html"
- class="multi-file-preview-holder"
- >
- </div>
- <div
- v-else-if="activeFile.tempFile"
- class="vertical-center render-error">
- <p class="text-center">
- The source could not be displayed for this temporary file.
- </p>
- </div>
- <div
- v-else-if="renderErrorTooLarge"
- class="vertical-center render-error">
- <p class="text-center">
- The source could not be displayed because it is too large.
- You can <a
- :href="activeFile.rawPath"
- download>download</a> it instead.
- </p>
- </div>
- <div
- v-else
- class="vertical-center render-error">
- <p class="text-center">
- The source could not be displayed because a rendering error occurred.
- You can <a
- :href="activeFile.rawPath"
- download>download</a> it instead.
- </p>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
deleted file mode 100644
index 5656081c598..00000000000
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<script>
- import { mapActions } from 'vuex';
- import fileIcon from '~/vue_shared/components/file_icon.vue';
-
- export default {
- components: {
- fileIcon,
- },
- props: {
- tab: {
- type: Object,
- required: true,
- },
- },
- computed: {
- closeLabel() {
- if (this.tab.changed || this.tab.tempFile) {
- return `${this.tab.name} changed`;
- }
- return `Close ${this.tab.name}`;
- },
- changedClass() {
- const tabChangedObj = {
- 'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
- 'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
- };
- return tabChangedObj;
- },
- },
-
- methods: {
- ...mapActions([
- 'closeFile',
- ]),
- clickFile(tab) {
- this.$router.push(`/project${tab.url}`);
- },
- },
- };
-</script>
-
-<template>
- <li @click="clickFile(tab)">
- <button
- type="button"
- class="multi-file-tab-close"
- @click.stop.prevent="closeFile({ file: tab })"
- :aria-label="closeLabel"
- :class="{
- 'modified': tab.changed,
- }"
- :disabled="tab.changed"
- >
- <i
- class="fa"
- :class="changedClass"
- aria-hidden="true"
- >
- </i>
- </button>
-
- <div
- class="multi-file-tab"
- :class="{active : tab.active }"
- :title="tab.url"
- >
- <file-icon
- :file-name="tab.name"
- :size="16"
- />
- {{ tab.name }}
- </div>
- </li>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue
deleted file mode 100644
index ca363bba0ef..00000000000
--- a/app/assets/javascripts/ide/components/repo_tabs.vue
+++ /dev/null
@@ -1,27 +0,0 @@
-<script>
- import { mapState } from 'vuex';
- import RepoTab from './repo_tab.vue';
-
- export default {
- components: {
- 'repo-tab': RepoTab,
- },
- computed: {
- ...mapState([
- 'openFiles',
- ]),
- },
- };
-</script>
-
-<template>
- <ul
- class="multi-file-tabs list-unstyled append-bottom-0"
- >
- <repo-tab
- v-for="tab in openFiles"
- :key="tab.key"
- :tab="tab"
- />
- </ul>
-</template>
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
deleted file mode 100644
index a7fb9e0588a..00000000000
--- a/app/assets/javascripts/ide/ide_router.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import Vue from 'vue';
-import VueRouter from 'vue-router';
-import store from './stores';
-import flash from '../flash';
-import {
- getTreeEntry,
-} from './stores/utils';
-
-Vue.use(VueRouter);
-
-/**
- * Routes below /-/ide/:
-
-/project/h5bp/html5-boilerplate/blob/master
-/project/h5bp/html5-boilerplate/blob/master/app/js/test.js
-
-/project/h5bp/html5-boilerplate/mr/123
-/project/h5bp/html5-boilerplate/mr/123/app/js/test.js
-
-/workspace/123
-/workspace/project/h5bp/html5-boilerplate/blob/my-special-branch
-/workspace/project/h5bp/html5-boilerplate/mr/123
-
-/ = /workspace
-
-/settings
-*/
-
-// Unfortunately Vue Router doesn't work without at least a fake component
-// If you do only data handling
-const EmptyRouterComponent = {
- render(createElement) {
- return createElement('div');
- },
-};
-
-const router = new VueRouter({
- mode: 'history',
- base: `${gon.relative_url_root}/-/ide/`,
- routes: [
- {
- path: '/project/:namespace/:project',
- component: EmptyRouterComponent,
- children: [
- {
- path: ':targetmode/:branch/*',
- component: EmptyRouterComponent,
- },
- {
- path: 'mr/:mrid',
- component: EmptyRouterComponent,
- },
- ],
- },
- ],
-});
-
-router.beforeEach((to, from, next) => {
- if (to.params.namespace && to.params.project) {
- store.dispatch('getProjectData', {
- namespace: to.params.namespace,
- projectId: to.params.project,
- })
- .then(() => {
- const fullProjectId = `${to.params.namespace}/${to.params.project}`;
-
- if (to.params.branch) {
- store.dispatch('getBranchData', {
- projectId: fullProjectId,
- branchId: to.params.branch,
- });
-
- store.dispatch('getTreeData', {
- projectId: fullProjectId,
- branch: to.params.branch,
- endpoint: `/tree/${to.params.branch}`,
- })
- .then(() => {
- if (to.params[0]) {
- const treeEntry = getTreeEntry(store, `${to.params.namespace}/${to.params.project}/${to.params.branch}`, to.params[0]);
- if (treeEntry) {
- store.dispatch('handleTreeEntryAction', treeEntry);
- }
- }
- })
- .catch((e) => {
- flash('Error while loading the branch files. Please try again.', 'alert', document, null, false, true);
- throw e;
- });
- }
- })
- .catch((e) => {
- flash('Error while loading the project data. Please try again.', 'alert', document, null, false, true);
- throw e;
- });
- }
-
- next();
-});
-
-export default router;
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
deleted file mode 100644
index e8a19f47cee..00000000000
--- a/app/assets/javascripts/ide/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import Vue from 'vue';
-import ide from './components/ide.vue';
-import store from './stores';
-import router from './ide_router';
-import Translate from '../vue_shared/translate';
-
-function initIde(el) {
- if (!el) return null;
-
- return new Vue({
- el,
- store,
- router,
- components: {
- ide,
- },
- render(createElement) {
- return createElement('ide', {
- props: {
- emptyStateSvgPath: el.dataset.emptyStateSvgPath,
- },
- });
- },
- });
-}
-
-const ideElement = document.getElementById('ide');
-
-Vue.use(Translate);
-
-initIde(ideElement);
diff --git a/app/assets/javascripts/ide/lib/common/disposable.js b/app/assets/javascripts/ide/lib/common/disposable.js
deleted file mode 100644
index 84b29bdb600..00000000000
--- a/app/assets/javascripts/ide/lib/common/disposable.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export default class Disposable {
- constructor() {
- this.disposers = new Set();
- }
-
- add(...disposers) {
- disposers.forEach(disposer => this.disposers.add(disposer));
- }
-
- dispose() {
- this.disposers.forEach(disposer => disposer.dispose());
- this.disposers.clear();
- }
-}
diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
deleted file mode 100644
index 14d9fe4771e..00000000000
--- a/app/assets/javascripts/ide/lib/common/model.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/* global monaco */
-import Disposable from './disposable';
-
-export default class Model {
- constructor(monaco, file) {
- this.monaco = monaco;
- this.disposable = new Disposable();
- this.file = file;
- this.content = file.content !== '' ? file.content : file.raw;
-
- this.disposable.add(
- this.originalModel = this.monaco.editor.createModel(
- this.file.raw,
- undefined,
- new this.monaco.Uri(null, null, `original/${this.file.path}`),
- ),
- this.model = this.monaco.editor.createModel(
- this.content,
- undefined,
- new this.monaco.Uri(null, null, this.file.path),
- ),
- );
-
- this.events = new Map();
- }
-
- get url() {
- return this.model.uri.toString();
- }
-
- get language() {
- return this.model.getModeId();
- }
-
- get eol() {
- return this.model.getEOL() === '\n' ? 'LF' : 'CRLF';
- }
-
- get path() {
- return this.file.path;
- }
-
- getModel() {
- return this.model;
- }
-
- getOriginalModel() {
- return this.originalModel;
- }
-
- onChange(cb) {
- this.events.set(
- this.path,
- this.disposable.add(
- this.model.onDidChangeContent(e => cb(this.model, e)),
- ),
- );
- }
-
- dispose() {
- this.disposable.dispose();
- this.events.clear();
- }
-}
diff --git a/app/assets/javascripts/ide/lib/common/model_manager.js b/app/assets/javascripts/ide/lib/common/model_manager.js
deleted file mode 100644
index fd462252795..00000000000
--- a/app/assets/javascripts/ide/lib/common/model_manager.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import Disposable from './disposable';
-import Model from './model';
-
-export default class ModelManager {
- constructor(monaco) {
- this.monaco = monaco;
- this.disposable = new Disposable();
- this.models = new Map();
- }
-
- hasCachedModel(path) {
- return this.models.has(path);
- }
-
- addModel(file) {
- if (this.hasCachedModel(file.path)) {
- return this.models.get(file.path);
- }
-
- const model = new Model(this.monaco, file);
- this.models.set(model.path, model);
- this.disposable.add(model);
-
- return model;
- }
-
- dispose() {
- // dispose of all the models
- this.disposable.dispose();
- this.models.clear();
- }
-}
diff --git a/app/assets/javascripts/ide/lib/decorations/controller.js b/app/assets/javascripts/ide/lib/decorations/controller.js
deleted file mode 100644
index 0954b7973c4..00000000000
--- a/app/assets/javascripts/ide/lib/decorations/controller.js
+++ /dev/null
@@ -1,43 +0,0 @@
-export default class DecorationsController {
- constructor(editor) {
- this.editor = editor;
- this.decorations = new Map();
- this.editorDecorations = new Map();
- }
-
- getAllDecorationsForModel(model) {
- if (!this.decorations.has(model.url)) return [];
-
- const modelDecorations = this.decorations.get(model.url);
- const decorations = [];
-
- modelDecorations.forEach(val => decorations.push(...val));
-
- return decorations;
- }
-
- addDecorations(model, decorationsKey, decorations) {
- const decorationMap = this.decorations.get(model.url) || new Map();
-
- decorationMap.set(decorationsKey, decorations);
-
- this.decorations.set(model.url, decorationMap);
-
- this.decorate(model);
- }
-
- decorate(model) {
- const decorations = this.getAllDecorationsForModel(model);
- const oldDecorations = this.editorDecorations.get(model.url) || [];
-
- this.editorDecorations.set(
- model.url,
- this.editor.instance.deltaDecorations(oldDecorations, decorations),
- );
- }
-
- dispose() {
- this.decorations.clear();
- this.editorDecorations.clear();
- }
-}
diff --git a/app/assets/javascripts/ide/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js
deleted file mode 100644
index dc0b1c95e59..00000000000
--- a/app/assets/javascripts/ide/lib/diff/controller.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* global monaco */
-import { throttle } from 'underscore';
-import DirtyDiffWorker from './diff_worker';
-import Disposable from '../common/disposable';
-
-export const getDiffChangeType = (change) => {
- if (change.modified) {
- return 'modified';
- } else if (change.added) {
- return 'added';
- } else if (change.removed) {
- return 'removed';
- }
-
- return '';
-};
-
-export const getDecorator = change => ({
- range: new monaco.Range(
- change.lineNumber,
- 1,
- change.endLineNumber,
- 1,
- ),
- options: {
- isWholeLine: true,
- linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`,
- },
-});
-
-export default class DirtyDiffController {
- constructor(modelManager, decorationsController) {
- this.disposable = new Disposable();
- this.editorSimpleWorker = null;
- this.modelManager = modelManager;
- this.decorationsController = decorationsController;
- this.dirtyDiffWorker = new DirtyDiffWorker();
- this.throttledComputeDiff = throttle(this.computeDiff, 250);
- this.decorate = this.decorate.bind(this);
-
- this.dirtyDiffWorker.addEventListener('message', this.decorate);
- }
-
- attachModel(model) {
- model.onChange(() => this.throttledComputeDiff(model));
- }
-
- computeDiff(model) {
- this.dirtyDiffWorker.postMessage({
- path: model.path,
- originalContent: model.getOriginalModel().getValue(),
- newContent: model.getModel().getValue(),
- });
- }
-
- reDecorate(model) {
- this.decorationsController.decorate(model);
- }
-
- decorate({ data }) {
- const decorations = data.changes.map(change => getDecorator(change));
- this.decorationsController.addDecorations(data.path, 'dirtyDiff', decorations);
- }
-
- dispose() {
- this.disposable.dispose();
-
- this.dirtyDiffWorker.removeEventListener('message', this.decorate);
- this.dirtyDiffWorker.terminate();
- }
-}
diff --git a/app/assets/javascripts/ide/lib/diff/diff.js b/app/assets/javascripts/ide/lib/diff/diff.js
deleted file mode 100644
index 0e37f5c4704..00000000000
--- a/app/assets/javascripts/ide/lib/diff/diff.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { diffLines } from 'diff';
-
-// eslint-disable-next-line import/prefer-default-export
-export const computeDiff = (originalContent, newContent) => {
- const changes = diffLines(originalContent, newContent);
-
- let lineNumber = 1;
- return changes.reduce((acc, change) => {
- const findOnLine = acc.find(c => c.lineNumber === lineNumber);
-
- if (findOnLine) {
- Object.assign(findOnLine, change, {
- modified: true,
- endLineNumber: (lineNumber + change.count) - 1,
- });
- } else if ('added' in change || 'removed' in change) {
- acc.push(Object.assign({}, change, {
- lineNumber,
- modified: undefined,
- endLineNumber: (lineNumber + change.count) - 1,
- }));
- }
-
- if (!change.removed) {
- lineNumber += change.count;
- }
-
- return acc;
- }, []);
-};
diff --git a/app/assets/javascripts/ide/lib/diff/diff_worker.js b/app/assets/javascripts/ide/lib/diff/diff_worker.js
deleted file mode 100644
index e74c4046330..00000000000
--- a/app/assets/javascripts/ide/lib/diff/diff_worker.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { computeDiff } from './diff';
-
-self.addEventListener('message', (e) => {
- const data = e.data;
-
- self.postMessage({
- path: data.path,
- changes: computeDiff(data.originalContent, data.newContent),
- });
-});
diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js
deleted file mode 100644
index 51255f15658..00000000000
--- a/app/assets/javascripts/ide/lib/editor.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import _ from 'underscore';
-import DecorationsController from './decorations/controller';
-import DirtyDiffController from './diff/controller';
-import Disposable from './common/disposable';
-import ModelManager from './common/model_manager';
-import editorOptions from './editor_options';
-
-export default class Editor {
- static create(monaco) {
- this.editorInstance = new Editor(monaco);
-
- return this.editorInstance;
- }
-
- constructor(monaco) {
- this.monaco = monaco;
- this.currentModel = null;
- this.instance = null;
- this.dirtyDiffController = null;
- this.disposable = new Disposable();
-
- this.disposable.add(
- this.modelManager = new ModelManager(this.monaco),
- this.decorationsController = new DecorationsController(this),
- );
-
- this.debouncedUpdate = _.debounce(() => {
- this.updateDimensions();
- }, 200);
- window.addEventListener('resize', this.debouncedUpdate, false);
- }
-
- createInstance(domElement) {
- if (!this.instance) {
- this.disposable.add(
- this.instance = this.monaco.editor.create(domElement, {
- model: null,
- readOnly: false,
- contextmenu: true,
- scrollBeyondLastLine: false,
- minimap: {
- enabled: false,
- },
- }),
- this.dirtyDiffController = new DirtyDiffController(
- this.modelManager, this.decorationsController,
- ),
- );
- }
- }
-
- createModel(file) {
- return this.modelManager.addModel(file);
- }
-
- attachModel(model) {
- this.instance.setModel(model.getModel());
- if (this.dirtyDiffController) this.dirtyDiffController.attachModel(model);
-
- this.currentModel = model;
-
- this.instance.updateOptions(editorOptions.reduce((acc, obj) => {
- Object.keys(obj).forEach((key) => {
- Object.assign(acc, {
- [key]: obj[key](model),
- });
- });
- return acc;
- }, {}));
-
- if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model);
- }
-
- clearEditor() {
- if (this.instance) {
- this.instance.setModel(null);
- }
- }
-
- dispose() {
- this.disposable.dispose();
- window.removeEventListener('resize', this.debouncedUpdate);
-
- // dispose main monaco instance
- if (this.instance) {
- this.instance = null;
- }
- }
-
- updateDimensions() {
- this.instance.layout();
- }
-
- setPosition({ lineNumber, column }) {
- this.instance.revealPositionInCenter({
- lineNumber,
- column,
- });
- this.instance.setPosition({
- lineNumber,
- column,
- });
- }
-
- onPositionChange(cb) {
- this.disposable.add(
- this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)),
- );
- }
-}
diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js
deleted file mode 100644
index 701affc466e..00000000000
--- a/app/assets/javascripts/ide/lib/editor_options.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export default [{
-}];
diff --git a/app/assets/javascripts/ide/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js
deleted file mode 100644
index 142a220097b..00000000000
--- a/app/assets/javascripts/ide/monaco_loader.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import monacoContext from 'monaco-editor/dev/vs/loader';
-
-monacoContext.require.config({
- paths: {
- vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
- },
-});
-
-// ignore CDN config and use local assets path for service worker which cannot be cross-domain
-const relativeRootPath = (gon && gon.relative_url_root) || '';
-const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`;
-window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` };
-
-// eslint-disable-next-line no-underscore-dangle
-window.__monaco_context__ = monacoContext;
-export default monacoContext.require;
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
deleted file mode 100644
index 1fb24e93f2e..00000000000
--- a/app/assets/javascripts/ide/services/index.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-import Api from '../../api';
-
-Vue.use(VueResource);
-
-export default {
- getTreeData(endpoint) {
- return Vue.http.get(endpoint, { params: { format: 'json' } });
- },
- getFileData(endpoint) {
- return Vue.http.get(endpoint, { params: { format: 'json' } });
- },
- getRawFileData(file) {
- if (file.tempFile) {
- return Promise.resolve(file.content);
- }
-
- if (file.raw) {
- return Promise.resolve(file.raw);
- }
-
- return Vue.http.get(file.rawPath, { params: { format: 'json' } })
- .then(res => res.text());
- },
- getProjectData(namespace, project) {
- return Api.project(`${namespace}/${project}`);
- },
- getBranchData(projectId, currentBranchId) {
- return Api.branchSingle(projectId, currentBranchId);
- },
- createBranch(projectId, payload) {
- const url = Api.buildUrl(Api.createBranchPath).replace(':id', projectId);
-
- return Vue.http.post(url, payload);
- },
- commit(projectId, payload) {
- return Api.commitMultiple(projectId, payload);
- },
- getTreeLastCommit(endpoint) {
- return Vue.http.get(endpoint, {
- params: {
- format: 'json',
- },
- });
- },
-};
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
deleted file mode 100644
index 2c690b1f635..00000000000
--- a/app/assets/javascripts/ide/stores/actions.js
+++ /dev/null
@@ -1,196 +0,0 @@
-import Vue from 'vue';
-import { visitUrl } from '~/lib/utils/url_utility';
-import flash from '~/flash';
-import service from '../services';
-import * as types from './mutation_types';
-import { stripHtml } from '../../lib/utils/text_utility';
-
-export const redirectToUrl = (_, url) => visitUrl(url);
-
-export const setInitialData = ({ commit }, data) =>
- commit(types.SET_INITIAL_DATA, data);
-
-export const closeDiscardPopup = ({ commit }) =>
- commit(types.TOGGLE_DISCARD_POPUP, false);
-
-export const discardAllChanges = ({ commit, getters, dispatch }) => {
- const changedFiles = getters.changedFiles;
-
- changedFiles.forEach((file) => {
- commit(types.DISCARD_FILE_CHANGES, file);
-
- if (file.tempFile) {
- dispatch('closeFile', { file, force: true });
- }
- });
-};
-
-export const closeAllFiles = ({ state, dispatch }) => {
- state.openFiles.forEach(file => dispatch('closeFile', { file }));
-};
-
-export const toggleEditMode = (
- { state, commit, getters, dispatch },
- force = false,
-) => {
- const changedFiles = getters.changedFiles;
-
- if (changedFiles.length && !force) {
- commit(types.TOGGLE_DISCARD_POPUP, true);
- } else {
- commit(types.TOGGLE_EDIT_MODE);
- commit(types.TOGGLE_DISCARD_POPUP, false);
- dispatch('toggleBlobView');
-
- if (!state.editMode) {
- dispatch('discardAllChanges');
- }
- }
-};
-
-export const toggleBlobView = ({ commit, state }) => {
- if (state.editMode) {
- commit(types.SET_EDIT_MODE);
- } else {
- commit(types.SET_PREVIEW_MODE);
- }
-};
-
-export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
- if (side === 'left') {
- commit(types.SET_LEFT_PANEL_COLLAPSED, collapsed);
- } else {
- commit(types.SET_RIGHT_PANEL_COLLAPSED, collapsed);
- }
-};
-
-export const setResizingStatus = ({ commit }, resizing) => {
- commit(types.SET_RESIZING_STATUS, resizing);
-};
-
-export const checkCommitStatus = ({ state }) =>
- service
- .getBranchData(state.currentProjectId, state.currentBranchId)
- .then(({ data }) => {
- const { id } = data.commit;
- const selectedBranch =
- state.projects[state.currentProjectId].branches[state.currentBranchId];
-
- if (selectedBranch.workingReference !== id) {
- return true;
- }
-
- return false;
- })
- .catch(() => flash('Error checking branch data. Please try again.', 'alert', document, null, false, true));
-
-export const commitChanges = (
- { commit, state, dispatch, getters },
- { payload, newMr },
-) =>
- service
- .commit(state.currentProjectId, payload)
- .then(({ data }) => {
- const { branch } = payload;
- if (!data.short_id) {
- flash(data.message, 'alert', document, null, false, true);
- return;
- }
-
- const selectedProject = state.projects[state.currentProjectId];
- const lastCommit = {
- commit_path: `${selectedProject.web_url}/commit/${data.id}`,
- commit: {
- message: data.message,
- authored_date: data.committed_date,
- },
- };
-
- let commitMsg = `Your changes have been committed. Commit ${data.short_id}`;
- if (data.stats) {
- commitMsg += ` with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`;
- }
-
- flash(
- commitMsg,
- 'notice',
- document,
- null,
- false,
- true);
- window.dispatchEvent(new Event('resize'));
-
- if (newMr) {
- dispatch('discardAllChanges');
- dispatch(
- 'redirectToUrl',
- `${selectedProject.web_url}/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
- );
- } else {
- commit(types.SET_BRANCH_WORKING_REFERENCE, {
- projectId: state.currentProjectId,
- branchId: state.currentBranchId,
- reference: data.id,
- });
-
- getters.changedFiles.forEach((entry) => {
- commit(types.SET_LAST_COMMIT_DATA, {
- entry,
- lastCommit,
- });
- });
-
- dispatch('discardAllChanges');
-
- window.scrollTo(0, 0);
- }
- })
- .catch((err) => {
- let errMsg = 'Error committing changes. Please try again.';
- if (err.response.data && err.response.data.message) {
- errMsg += ` (${stripHtml(err.response.data.message)})`;
- }
- flash(errMsg, 'alert', document, null, false, true);
- window.dispatchEvent(new Event('resize'));
- });
-
-export const createTempEntry = (
- { state, dispatch },
- { projectId, branchId, parent, name, type, content = '', base64 = false },
-) => {
- const selectedParent = parent || state.trees[`${projectId}/${branchId}`];
- if (type === 'tree') {
- dispatch('createTempTree', {
- projectId,
- branchId,
- parent: selectedParent,
- name,
- });
- } else if (type === 'blob') {
- dispatch('createTempFile', {
- projectId,
- branchId,
- parent: selectedParent,
- name,
- base64,
- content,
- });
- }
-};
-
-export const scrollToTab = () => {
- Vue.nextTick(() => {
- const tabs = document.getElementById('tabs');
-
- if (tabs) {
- const tabEl = tabs.querySelector('.active .repo-tab');
-
- tabEl.focus();
- }
- });
-};
-
-export * from './actions/tree';
-export * from './actions/file';
-export * from './actions/project';
-export * from './actions/branch';
diff --git a/app/assets/javascripts/ide/stores/actions/branch.js b/app/assets/javascripts/ide/stores/actions/branch.js
deleted file mode 100644
index bc6fd2d4163..00000000000
--- a/app/assets/javascripts/ide/stores/actions/branch.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import service from '../../services';
-import flash from '../../../flash';
-import * as types from '../mutation_types';
-
-export const getBranchData = (
- { commit, state, dispatch },
- { projectId, branchId, force = false } = {},
-) => new Promise((resolve, reject) => {
- if ((typeof state.projects[`${projectId}`] === 'undefined' ||
- !state.projects[`${projectId}`].branches[branchId])
- || force) {
- service.getBranchData(`${projectId}`, branchId)
- .then(({ data }) => {
- const { id } = data.commit;
- commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
- commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
- resolve(data);
- })
- .catch(() => {
- flash('Error loading branch data. Please try again.', 'alert', document, null, false, true);
- reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
- });
- } else {
- resolve(state.projects[`${projectId}`].branches[branchId]);
- }
-});
-
-export const createNewBranch = ({ state, commit }, branch) => service.createBranch(
- state.currentProjectId,
- {
- branch,
- ref: state.currentBranchId,
- },
-)
-.then(res => res.json())
-.then((data) => {
- const branchName = data.name;
- const url = location.href.replace(state.currentBranchId, branchName);
-
- if (this.$router) this.$router.push(url);
-
- commit(types.SET_CURRENT_BRANCH, branchName);
-});
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
deleted file mode 100644
index 670af2fb89e..00000000000
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import { normalizeHeaders } from '../../../lib/utils/common_utils';
-import flash from '../../../flash';
-import service from '../../services';
-import * as types from '../mutation_types';
-import router from '../../ide_router';
-import {
- findEntry,
- setPageTitle,
- createTemp,
- findIndexOfFile,
-} from '../utils';
-
-export const closeFile = ({ commit, state, dispatch }, { file, force = false }) => {
- if ((file.changed || file.tempFile) && !force) return;
-
- const indexOfClosedFile = findIndexOfFile(state.openFiles, file);
- const fileWasActive = file.active;
-
- commit(types.TOGGLE_FILE_OPEN, file);
- commit(types.SET_FILE_ACTIVE, { file, active: false });
-
- if (state.openFiles.length > 0 && fileWasActive) {
- const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
- const nextFileToOpen = state.openFiles[nextIndexToOpen];
-
- dispatch('setFileActive', nextFileToOpen);
- } else if (!state.openFiles.length) {
- router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
- }
-
- dispatch('getLastCommitData');
-};
-
-export const setFileActive = ({ commit, state, getters, dispatch }, file) => {
- const currentActiveFile = getters.activeFile;
-
- if (file.active) return;
-
- if (currentActiveFile) {
- commit(types.SET_FILE_ACTIVE, { file: currentActiveFile, active: false });
- }
-
- commit(types.SET_FILE_ACTIVE, { file, active: true });
- dispatch('scrollToTab');
-
- // reset hash for line highlighting
- location.hash = '';
-
- commit(types.SET_CURRENT_PROJECT, file.projectId);
- commit(types.SET_CURRENT_BRANCH, file.branchId);
-};
-
-export const getFileData = ({ state, commit, dispatch }, file) => {
- commit(types.TOGGLE_LOADING, file);
-
- service.getFileData(file.url)
- .then((res) => {
- const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
-
- setPageTitle(pageTitle);
-
- return res.json();
- })
- .then((data) => {
- commit(types.SET_FILE_DATA, { data, file });
- commit(types.TOGGLE_FILE_OPEN, file);
- dispatch('setFileActive', file);
- commit(types.TOGGLE_LOADING, file);
- })
- .catch(() => {
- commit(types.TOGGLE_LOADING, file);
- flash('Error loading file data. Please try again.', 'alert', document, null, false, true);
- });
-};
-
-export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file)
- .then((raw) => {
- commit(types.SET_FILE_RAW_DATA, { file, raw });
- })
- .catch(() => flash('Error loading file content. Please try again.', 'alert', document, null, false, true));
-
-export const changeFileContent = ({ commit }, { file, content }) => {
- commit(types.UPDATE_FILE_CONTENT, { file, content });
-};
-
-export const setFileLanguage = ({ state, commit }, { fileLanguage }) => {
- if (state.selectedFile) {
- commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage });
- }
-};
-
-export const setFileEOL = ({ state, commit }, { eol }) => {
- if (state.selectedFile) {
- commit(types.SET_FILE_EOL, { file: state.selectedFile, eol });
- }
-};
-
-export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => {
- if (state.selectedFile) {
- commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn });
- }
-};
-
-export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => {
- const path = parent.path !== undefined ? parent.path : '';
- // We need to do the replacement otherwise the web_url + file.url duplicate
- const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`;
- const file = createTemp({
- projectId,
- branchId,
- name: name.replace(`${path}/`, ''),
- path,
- type: 'blob',
- level: parent.level !== undefined ? parent.level + 1 : 0,
- changed: true,
- content,
- base64,
- url: newUrl,
- });
-
- if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`, 'alert', document, null, false, true);
-
- commit(types.CREATE_TMP_FILE, {
- parent,
- file,
- });
- commit(types.TOGGLE_FILE_OPEN, file);
- dispatch('setFileActive', file);
-
- if (!state.editMode && !file.base64) {
- dispatch('toggleEditMode', true);
- }
-
- router.push(`/project${file.url}`);
-
- return Promise.resolve(file);
-};
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
deleted file mode 100644
index faeceb430a2..00000000000
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import service from '../../services';
-import flash from '../../../flash';
-import * as types from '../mutation_types';
-
-// eslint-disable-next-line import/prefer-default-export
-export const getProjectData = (
- { commit, state, dispatch },
- { namespace, projectId, force = false } = {},
-) => new Promise((resolve, reject) => {
- if (!state.projects[`${namespace}/${projectId}`] || force) {
- commit(types.TOGGLE_LOADING, state);
- service.getProjectData(namespace, projectId)
- .then(res => res.data)
- .then((data) => {
- commit(types.TOGGLE_LOADING, state);
- commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
- if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
- resolve(data);
- })
- .catch(() => {
- flash('Error loading project data. Please try again.', 'alert', document, null, false, true);
- reject(new Error(`Project not loaded ${namespace}/${projectId}`));
- });
- } else {
- resolve(state.projects[`${namespace}/${projectId}`]);
- }
-});
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
deleted file mode 100644
index 302ba45edee..00000000000
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ /dev/null
@@ -1,188 +0,0 @@
-import { visitUrl } from '../../../lib/utils/url_utility';
-import { normalizeHeaders } from '../../../lib/utils/common_utils';
-import flash from '../../../flash';
-import service from '../../services';
-import * as types from '../mutation_types';
-import router from '../../ide_router';
-import {
- setPageTitle,
- findEntry,
- createTemp,
- createOrMergeEntry,
-} from '../utils';
-
-export const getTreeData = (
- { commit, state, dispatch },
- { endpoint, tree = null, projectId, branch, force = false } = {},
-) => new Promise((resolve, reject) => {
- // We already have the base tree so we resolve immediately
- if (!tree && state.trees[`${projectId}/${branch}`] && !force) {
- resolve();
- } else {
- if (tree) commit(types.TOGGLE_LOADING, tree);
- const selectedProject = state.projects[projectId];
- // We are merging the web_url that we got on the project info with the endpoint
- // we got on the tree entry, as both contain the projectId, we replace it in the tree endpoint
- const completeEndpoint = selectedProject.web_url + (endpoint).replace(projectId, '');
- if (completeEndpoint && (!tree || !tree.tempFile)) {
- service.getTreeData(completeEndpoint)
- .then((res) => {
- const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
-
- setPageTitle(pageTitle);
-
- return res.json();
- })
- .then((data) => {
- if (!state.isInitialRoot) {
- commit(types.SET_ROOT, data.path === '/');
- }
-
- dispatch('updateDirectoryData', { data, tree, projectId, branch });
- const selectedTree = tree || state.trees[`${projectId}/${branch}`];
-
- commit(types.SET_PARENT_TREE_URL, data.parent_tree_url);
- commit(types.SET_LAST_COMMIT_URL, { tree: selectedTree, url: data.last_commit_path });
- if (tree) commit(types.TOGGLE_LOADING, selectedTree);
-
- const prevLastCommitPath = selectedTree.lastCommitPath;
- if (prevLastCommitPath !== null) {
- dispatch('getLastCommitData', selectedTree);
- }
- resolve(data);
- })
- .catch((e) => {
- flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
- if (tree) commit(types.TOGGLE_LOADING, tree);
- reject(e);
- });
- } else {
- resolve();
- }
- }
-});
-
-export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => {
- if (tree.opened) {
- // send empty data to clear the tree
- const data = { trees: [], blobs: [], submodules: [] };
-
- dispatch('updateDirectoryData', { data, tree, projectId: tree.projectId, branchId: tree.branchId });
- } else {
- dispatch('getTreeData', { endpoint, tree, projectId: tree.projectId, branch: tree.branchId });
- }
-
- commit(types.TOGGLE_TREE_OPEN, tree);
-};
-
-export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
- if (row.type === 'tree') {
- dispatch('toggleTreeOpen', {
- endpoint: row.url,
- tree: row,
- });
- } else if (row.type === 'submodule') {
- commit(types.TOGGLE_LOADING, row);
- visitUrl(row.url);
- } else if (row.type === 'blob' && row.opened) {
- dispatch('setFileActive', row);
- } else {
- dispatch('getFileData', row);
- }
-};
-
-export const createTempTree = (
- { state, commit, dispatch },
- { projectId, branchId, parent, name },
-) => {
- let selectedTree = parent;
- const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
-
- dirNames.forEach((dirName) => {
- const foundEntry = findEntry(selectedTree.tree, 'tree', dirName);
-
- if (!foundEntry) {
- const path = selectedTree.path !== undefined ? selectedTree.path : '';
- const tmpEntry = createTemp({
- projectId,
- branchId,
- name: dirName,
- path,
- type: 'tree',
- level: selectedTree.level !== undefined ? selectedTree.level + 1 : 0,
- tree: [],
- url: `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${dirName}`,
- });
-
- commit(types.CREATE_TMP_TREE, {
- parent: selectedTree,
- tmpEntry,
- });
- commit(types.TOGGLE_TREE_OPEN, tmpEntry);
-
- router.push(`/project${tmpEntry.url}`);
-
- selectedTree = tmpEntry;
- } else {
- selectedTree = foundEntry;
- }
- });
-};
-
-export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
- if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
-
- service.getTreeLastCommit(tree.lastCommitPath)
- .then((res) => {
- const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
-
- commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
-
- return res.json();
- })
- .then((data) => {
- data.forEach((lastCommit) => {
- const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name);
-
- if (entry) {
- commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit });
- }
- });
-
- dispatch('getLastCommitData', tree);
- })
- .catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
-};
-
-export const updateDirectoryData = (
- { commit, state },
- { data, tree, projectId, branch },
-) => {
- if (!tree) {
- const existingTree = state.trees[`${projectId}/${branch}`];
- if (!existingTree) {
- commit(types.CREATE_TREE, { treePath: `${projectId}/${branch}` });
- }
- }
-
- const selectedTree = tree || state.trees[`${projectId}/${branch}`];
- const level = selectedTree.level !== undefined ? selectedTree.level + 1 : 0;
- const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl;
- const createEntry = (entry, type) => createOrMergeEntry({
- tree: selectedTree,
- projectId: `${projectId}`,
- branchId: branch,
- entry,
- level,
- type,
- parentTreeUrl,
- });
-
- const formattedData = [
- ...data.trees.map(t => createEntry(t, 'tree')),
- ...data.submodules.map(m => createEntry(m, 'submodule')),
- ...data.blobs.map(b => createEntry(b, 'blob')),
- ];
-
- commit(types.SET_DIRECTORY_DATA, { tree: selectedTree, data: formattedData });
-};
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
deleted file mode 100644
index 6b51ccff817..00000000000
--- a/app/assets/javascripts/ide/stores/getters.js
+++ /dev/null
@@ -1,19 +0,0 @@
-export const changedFiles = state => state.openFiles.filter(file => file.changed);
-
-export const activeFile = state => state.openFiles.find(file => file.active) || null;
-
-export const activeFileExtension = (state) => {
- const file = activeFile(state);
- return file ? `.${file.path.split('.').pop()}` : '';
-};
-
-export const canEditFile = (state) => {
- const currentActiveFile = activeFile(state);
-
- return state.canCommit &&
- (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
-};
-
-export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
-
-export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
deleted file mode 100644
index 6ac9bfd8189..00000000000
--- a/app/assets/javascripts/ide/stores/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import state from './state';
-import * as actions from './actions';
-import * as getters from './getters';
-import mutations from './mutations';
-
-Vue.use(Vuex);
-
-export default new Vuex.Store({
- state: state(),
- actions,
- mutations,
- getters,
-});
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
deleted file mode 100644
index 69b218a5e7d..00000000000
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ /dev/null
@@ -1,46 +0,0 @@
-export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
-export const TOGGLE_LOADING = 'TOGGLE_LOADING';
-export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL';
-export const SET_ROOT = 'SET_ROOT';
-export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
-export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
-export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
-export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
-
-// Project Mutation Types
-export const SET_PROJECT = 'SET_PROJECT';
-export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
-export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
-
-// Branch Mutation Types
-export const SET_BRANCH = 'SET_BRANCH';
-export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
-export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
-
-// Tree mutation types
-export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA';
-export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN';
-export const CREATE_TMP_TREE = 'CREATE_TMP_TREE';
-export const SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL';
-export const CREATE_TREE = 'CREATE_TREE';
-
-// File mutation types
-export const SET_FILE_DATA = 'SET_FILE_DATA';
-export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN';
-export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE';
-export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA';
-export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
-export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
-export const SET_FILE_POSITION = 'SET_FILE_POSITION';
-export const SET_FILE_EOL = 'SET_FILE_EOL';
-export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
-export const CREATE_TMP_FILE = 'CREATE_TMP_FILE';
-
-// Viewer mutation types
-export const SET_PREVIEW_MODE = 'SET_PREVIEW_MODE';
-export const SET_EDIT_MODE = 'SET_EDIT_MODE';
-export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
-export const TOGGLE_DISCARD_POPUP = 'TOGGLE_DISCARD_POPUP';
-
-export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH';
-
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
deleted file mode 100644
index 03d81be10a1..00000000000
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import * as types from './mutation_types';
-import projectMutations from './mutations/project';
-import fileMutations from './mutations/file';
-import treeMutations from './mutations/tree';
-import branchMutations from './mutations/branch';
-
-export default {
- [types.SET_INITIAL_DATA](state, data) {
- Object.assign(state, data);
- },
- [types.SET_PREVIEW_MODE](state) {
- Object.assign(state, {
- currentBlobView: 'repo-preview',
- });
- },
- [types.SET_EDIT_MODE](state) {
- Object.assign(state, {
- currentBlobView: 'repo-editor',
- });
- },
- [types.TOGGLE_LOADING](state, entry) {
- Object.assign(entry, {
- loading: !entry.loading,
- });
- },
- [types.TOGGLE_EDIT_MODE](state) {
- Object.assign(state, {
- editMode: !state.editMode,
- });
- },
- [types.TOGGLE_DISCARD_POPUP](state, discardPopupOpen) {
- Object.assign(state, {
- discardPopupOpen,
- });
- },
- [types.SET_ROOT](state, isRoot) {
- Object.assign(state, {
- isRoot,
- isInitialRoot: isRoot,
- });
- },
- [types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) {
- Object.assign(state, {
- leftPanelCollapsed: collapsed,
- });
- },
- [types.SET_RIGHT_PANEL_COLLAPSED](state, collapsed) {
- Object.assign(state, {
- rightPanelCollapsed: collapsed,
- });
- },
- [types.SET_RESIZING_STATUS](state, resizing) {
- Object.assign(state, {
- panelResizing: resizing,
- });
- },
- [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
- Object.assign(entry.lastCommit, {
- id: lastCommit.commit.id,
- url: lastCommit.commit_path,
- message: lastCommit.commit.message,
- author: lastCommit.commit.author_name,
- updatedAt: lastCommit.commit.authored_date,
- });
- },
- ...projectMutations,
- ...fileMutations,
- ...treeMutations,
- ...branchMutations,
-};
diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js
deleted file mode 100644
index 04b9582c5bb..00000000000
--- a/app/assets/javascripts/ide/stores/mutations/branch.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as types from '../mutation_types';
-
-export default {
- [types.SET_CURRENT_BRANCH](state, currentBranchId) {
- Object.assign(state, {
- currentBranchId,
- });
- },
- [types.SET_BRANCH](state, { projectPath, branchName, branch }) {
- // Add client side properties
- Object.assign(branch, {
- treeId: `${projectPath}/${branchName}`,
- active: true,
- workingReference: '',
- });
-
- Object.assign(state.projects[projectPath], {
- branches: {
- [branchName]: branch,
- },
- });
- },
- [types.SET_BRANCH_WORKING_REFERENCE](state, { projectId, branchId, reference }) {
- Object.assign(state.projects[projectId].branches[branchId], {
- workingReference: reference,
- });
- },
-};
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
deleted file mode 100644
index 72db1c180c9..00000000000
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as types from '../mutation_types';
-import { findIndexOfFile } from '../utils';
-
-export default {
- [types.SET_FILE_ACTIVE](state, { file, active }) {
- Object.assign(file, {
- active,
- });
-
- Object.assign(state, {
- selectedFile: file,
- });
- },
- [types.TOGGLE_FILE_OPEN](state, file) {
- Object.assign(file, {
- opened: !file.opened,
- });
-
- if (file.opened) {
- state.openFiles.push(file);
- } else {
- state.openFiles.splice(findIndexOfFile(state.openFiles, file), 1);
- }
- },
- [types.SET_FILE_DATA](state, { data, file }) {
- Object.assign(file, {
- blamePath: data.blame_path,
- commitsPath: data.commits_path,
- permalink: data.permalink,
- rawPath: data.raw_path,
- binary: data.binary,
- html: data.html,
- renderError: data.render_error,
- });
- },
- [types.SET_FILE_RAW_DATA](state, { file, raw }) {
- Object.assign(file, {
- raw,
- });
- },
- [types.UPDATE_FILE_CONTENT](state, { file, content }) {
- const changed = content !== file.raw;
-
- Object.assign(file, {
- content,
- changed,
- });
- },
- [types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) {
- Object.assign(file, {
- fileLanguage,
- });
- },
- [types.SET_FILE_EOL](state, { file, eol }) {
- Object.assign(file, {
- eol,
- });
- },
- [types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) {
- Object.assign(file, {
- editorRow,
- editorColumn,
- });
- },
- [types.DISCARD_FILE_CHANGES](state, file) {
- Object.assign(file, {
- content: file.raw,
- changed: false,
- });
- },
- [types.CREATE_TMP_FILE](state, { file, parent }) {
- parent.tree.push(file);
- },
-};
diff --git a/app/assets/javascripts/ide/stores/mutations/project.js b/app/assets/javascripts/ide/stores/mutations/project.js
deleted file mode 100644
index 2816562a919..00000000000
--- a/app/assets/javascripts/ide/stores/mutations/project.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as types from '../mutation_types';
-
-export default {
- [types.SET_CURRENT_PROJECT](state, currentProjectId) {
- Object.assign(state, {
- currentProjectId,
- });
- },
- [types.SET_PROJECT](state, { projectPath, project }) {
- // Add client side properties
- Object.assign(project, {
- tree: [],
- branches: {},
- active: true,
- });
-
- Object.assign(state, {
- projects: Object.assign({}, state.projects, {
- [projectPath]: project,
- }),
- });
- },
-};
diff --git a/app/assets/javascripts/ide/stores/mutations/tree.js b/app/assets/javascripts/ide/stores/mutations/tree.js
deleted file mode 100644
index 4fe438ab465..00000000000
--- a/app/assets/javascripts/ide/stores/mutations/tree.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import * as types from '../mutation_types';
-
-export default {
- [types.TOGGLE_TREE_OPEN](state, tree) {
- Object.assign(tree, {
- opened: !tree.opened,
- });
- },
- [types.CREATE_TREE](state, { treePath }) {
- Object.assign(state, {
- trees: Object.assign({}, state.trees, {
- [treePath]: {
- tree: [],
- },
- }),
- });
- },
- [types.SET_DIRECTORY_DATA](state, { data, tree }) {
- Object.assign(tree, {
- tree: data,
- });
- },
- [types.SET_PARENT_TREE_URL](state, url) {
- Object.assign(state, {
- parentTreeUrl: url,
- });
- },
- [types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
- Object.assign(tree, {
- lastCommitPath: url,
- });
- },
- [types.CREATE_TMP_TREE](state, { parent, tmpEntry }) {
- parent.tree.push(tmpEntry);
- },
-};
diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js
deleted file mode 100644
index 61d12096946..00000000000
--- a/app/assets/javascripts/ide/stores/state.js
+++ /dev/null
@@ -1,23 +0,0 @@
-export default () => ({
- canCommit: false,
- currentProjectId: '',
- currentBranchId: '',
- currentBlobView: 'repo-editor',
- discardPopupOpen: false,
- editMode: true,
- endpoints: {},
- isRoot: false,
- isInitialRoot: false,
- lastCommitPath: '',
- loading: false,
- onTopOfBranch: false,
- openFiles: [],
- selectedFile: null,
- path: '',
- parentTreeUrl: '',
- trees: {},
- projects: {},
- leftPanelCollapsed: false,
- rightPanelCollapsed: true,
- panelResizing: false,
-});
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
deleted file mode 100644
index d556404faa5..00000000000
--- a/app/assets/javascripts/ide/stores/utils.js
+++ /dev/null
@@ -1,177 +0,0 @@
-import _ from 'underscore';
-
-export const dataStructure = () => ({
- id: '',
- key: '',
- type: '',
- projectId: '',
- branchId: '',
- name: '',
- url: '',
- path: '',
- level: 0,
- tempFile: false,
- icon: '',
- tree: [],
- loading: false,
- opened: false,
- active: false,
- changed: false,
- lastCommitPath: '',
- lastCommit: {
- id: '',
- url: '',
- message: '',
- updatedAt: '',
- author: '',
- },
- tree_url: '',
- blamePath: '',
- commitsPath: '',
- permalink: '',
- rawPath: '',
- binary: false,
- html: '',
- raw: '',
- content: '',
- parentTreeUrl: '',
- renderError: false,
- base64: false,
- editorRow: 1,
- editorColumn: 1,
- fileLanguage: '',
- eol: '',
-});
-
-export const decorateData = (entity) => {
- const {
- id,
- projectId,
- branchId,
- type,
- url,
- name,
- icon,
- tree_url,
- path,
- renderError,
- content = '',
- tempFile = false,
- active = false,
- opened = false,
- changed = false,
- parentTreeUrl = '',
- level = 0,
- base64 = false,
- } = entity;
-
- return {
- ...dataStructure(),
- id,
- projectId,
- branchId,
- key: `${name}-${type}-${id}`,
- type,
- name,
- url,
- tree_url,
- path,
- level,
- tempFile,
- icon: `fa-${icon}`,
- opened,
- active,
- parentTreeUrl,
- changed,
- renderError,
- content,
- base64,
- };
-};
-
-/*
- Takes the multi-dimensional tree and returns a flattened array.
- This allows for the table to recursively render the table rows but keeps the data
- structure nested to make it easier to add new files/directories.
-*/
-export const treeList = (state, treeId) => {
- const baseTree = state.trees[treeId];
- if (baseTree) {
- const mapTree = arr => (!arr.tree || !arr.tree.length ?
- [] : _.map(arr.tree, a => [a, mapTree(a)]));
-
- return _.chain(baseTree.tree)
- .map(arr => [arr, mapTree(arr)])
- .flatten()
- .value();
- }
- return [];
-};
-
-export const getTree = state => (namespace, projectId, branch) => state.trees[`${namespace}/${projectId}/${branch}`];
-
-export const getTreeEntry = (store, treeId, path) => {
- const fileList = treeList(store.state, treeId);
- return fileList ? fileList.find(file => file.path === path) : null;
-};
-
-export const findEntry = (tree, type, name) => tree.find(
- f => f.type === type && f.name === name,
-);
-
-export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
-
-export const setPageTitle = (title) => {
- document.title = title;
-};
-
-export const createTemp = ({
- projectId, branchId, name, path, type, level, changed, content, base64, url,
-}) => {
- const treePath = path ? `${path}/${name}` : name;
-
- return decorateData({
- id: new Date().getTime().toString(),
- projectId,
- branchId,
- name,
- type,
- tempFile: true,
- path: treePath,
- icon: type === 'tree' ? 'folder' : 'file-text-o',
- changed,
- content,
- parentTreeUrl: '',
- level,
- base64,
- renderError: base64,
- url,
- });
-};
-
-export const createOrMergeEntry = ({ tree,
- projectId,
- branchId,
- entry,
- type,
- parentTreeUrl,
- level }) => {
- const found = findEntry(tree.tree || tree, type, entry.name);
-
- if (found) {
- return Object.assign({}, found, {
- id: entry.id,
- url: entry.url,
- tempFile: false,
- });
- }
-
- return decorateData({
- ...entry,
- projectId,
- branchId,
- type,
- parentTreeUrl,
- level,
- });
-};
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index e741789fbb6..ed90db317df 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -302,6 +302,14 @@ export const parseQueryStringIntoObject = (query = '') => {
}, {});
};
+/**
+ * Converts object with key-value pairs
+ * into query-param string
+ *
+ * @param {Object} params
+ */
+export const objectToQueryString = (params = {}) => Object.keys(params).map(param => `${param}=${params[param]}`).join('&');
+
export const buildUrlWithCurrentLocation = param => (param ? `${window.location.pathname}${param}` : window.location.pathname);
/**
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 2841ecb558b..c259d5405bd 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -216,6 +216,9 @@ export default class MilestoneSelect {
$value.html(milestoneLinkNoneTemplate);
return $sidebarCollapsedValue.find('span').text('No');
}
+ })
+ .catch(() => {
+ $loading.fadeOut();
});
}
}
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index f4cba998fa7..972fdb2b791 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -3,7 +3,7 @@ import notesApp from '../notes/components/notes_app.vue';
import discussionCounter from '../notes/components/discussion_counter.vue';
import store from '../notes/stores';
-document.addEventListener('DOMContentLoaded', () => {
+export default function initMrNotes() {
new Vue({ // eslint-disable-line
el: '#js-vue-mr-discussions',
components: {
@@ -38,4 +38,4 @@ document.addEventListener('DOMContentLoaded', () => {
return createElement('discussion-counter');
},
});
-});
+}
diff --git a/app/assets/javascripts/pages/projects/environments/terminal/index.js b/app/assets/javascripts/pages/projects/environments/terminal/index.js
new file mode 100644
index 00000000000..7129e24cee1
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/environments/terminal/index.js
@@ -0,0 +1,3 @@
+import initTerminal from '~/terminal/';
+
+document.addEventListener('DOMContentLoaded', initTerminal);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
index 3e72f7a6f37..e5b2827b50c 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/show/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
@@ -1,7 +1,13 @@
+import { hasVueMRDiscussionsCookie } from '~/lib/utils/common_utils';
+import initMrNotes from '~/mr_notes';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initShow from '../init_merge_request_show';
document.addEventListener('DOMContentLoaded', () => {
initShow();
initSidebarBundle();
+
+ if (hasVueMRDiscussionsCookie()) {
+ initMrNotes();
+ }
});
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index 25dfa99ad9c..a84e2790680 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
import Translate from '../../../../vue_shared/translate';
+import { convertPermissionToBoolean } from '../../../../lib/utils/common_utils';
Vue.use(Translate);
@@ -11,16 +12,28 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
pipelinesComponent,
},
data() {
- const store = new PipelinesStore();
-
return {
- store,
+ store: new PipelinesStore(),
};
},
+ created() {
+ this.dataset = document.querySelector(this.$options.el).dataset;
+ },
render(createElement) {
return createElement('pipelines-component', {
props: {
store: this.store,
+ endpoint: this.dataset.endpoint,
+ helpPagePath: this.dataset.helpPagePath,
+ emptyStateSvgPath: this.dataset.emptyStateSvgPath,
+ errorStateSvgPath: this.dataset.errorStateSvgPath,
+ noPipelinesSvgPath: this.dataset.noPipelinesSvgPath,
+ autoDevopsPath: this.dataset.helpAutoDevopsPath,
+ newPipelinePath: this.dataset.newPipelinePath,
+ canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline),
+ hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi),
+ ciLintPath: this.dataset.ciLintPath,
+ resetCachePath: this.dataset.resetCachePath,
},
});
},
diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js
index 57f08701a4f..7fdf4ee0bf3 100644
--- a/app/assets/javascripts/pages/search/init_filtered_search.js
+++ b/app/assets/javascripts/pages/search/init_filtered_search.js
@@ -5,6 +5,7 @@ export default ({
filteredSearchTokenKeys,
isGroup,
isGroupAncestor,
+ isGroupDecendent,
stateFiltersSelector,
}) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
@@ -13,6 +14,7 @@ export default ({
page,
isGroup,
isGroupAncestor,
+ isGroupDecendent,
filteredSearchTokenKeys,
stateFiltersSelector,
});
diff --git a/app/assets/javascripts/pipelines/components/blank_state.vue b/app/assets/javascripts/pipelines/components/blank_state.vue
new file mode 100644
index 00000000000..8d3d6223d7b
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/blank_state.vue
@@ -0,0 +1,32 @@
+<script>
+ export default {
+ name: 'PipelinesSvgState',
+ props: {
+ svgPath: {
+ type: String,
+ required: true,
+ },
+
+ message: {
+ type: String,
+ required: true,
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="row empty-state">
+ <div class="col-xs-12">
+ <div class="svg-content">
+ <img :src="svgPath" />
+ </div>
+ </div>
+
+ <div class="col-xs-12 text-center">
+ <div class="text-content">
+ <h4>{{ message }}</h4>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue
index dfaa2574091..10ac8c08bed 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/empty_state.vue
@@ -1,5 +1,6 @@
<script>
export default {
+ name: 'PipelinesEmptyState',
props: {
helpPagePath: {
type: String,
@@ -9,6 +10,10 @@
type: String,
required: true,
},
+ canSetCi: {
+ type: Boolean,
+ required: true,
+ },
},
};
</script>
@@ -22,22 +27,36 @@
<div class="col-xs-12">
<div class="text-content">
- <h4 class="text-center">
- {{ s__("Pipelines|Build with confidence") }}
- </h4>
- <p>
- {{ s__(`Pipelines|Continous Integration can help
-catch bugs by running your tests automatically,
-while Continuous Deployment can help you deliver code to your product environment.`) }}
+
+ <template v-if="canSetCi">
+ <h4 class="text-center">
+ {{ s__('Pipelines|Build with confidence') }}
+ </h4>
+
+ <p>
+ {{ s__(`Pipelines|Continous Integration can help
+ catch bugs by running your tests automatically,
+ while Continuous Deployment can help you deliver
+ code to your product environment.`) }}
+ </p>
+
+ <div class="text-center">
+ <a
+ :href="helpPagePath"
+ class="btn btn-primary js-get-started-pipelines"
+ >
+ {{ s__('Pipelines|Get started with Pipelines') }}
+ </a>
+ </div>
+ </template>
+
+ <p
+ v-else
+ class="text-center"
+ >
+ {{ s__('Pipelines|This project is not currently set up to run pipelines.') }}
</p>
- <div class="text-center">
- <a
- :href="helpPagePath"
- class="btn btn-info"
- >
- {{ s__("Pipelines|Get started with Pipelines") }}
- </a>
- </div>
+
</div>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/error_state.vue b/app/assets/javascripts/pipelines/components/error_state.vue
deleted file mode 100644
index 012853b201d..00000000000
--- a/app/assets/javascripts/pipelines/components/error_state.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-export default {
- props: {
- errorStateSvgPath: {
- type: String,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div class="row empty-state js-pipelines-error-state">
- <div class="col-xs-12">
- <div class="svg-content">
- <img :src="errorStateSvgPath"/>
- </div>
- </div>
-
- <div class="col-xs-12 text-center">
- <div class="text-content">
- <h4>The API failed to fetch the pipelines.</h4>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue
index f31a91c3403..383ab51fe56 100644
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/nav_controls.vue
@@ -1,67 +1,52 @@
<script>
-export default {
- name: 'PipelineNavControls',
- props: {
- newPipelinePath: {
- type: String,
- required: true,
+ export default {
+ name: 'PipelineNavControls',
+ props: {
+ newPipelinePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+
+ resetCachePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+
+ ciLintPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
-
- hasCiEnabled: {
- type: Boolean,
- required: true,
- },
-
- helpPagePath: {
- type: String,
- required: true,
- },
-
- resetCachePath: {
- type: String,
- required: true,
- },
-
- ciLintPath: {
- type: String,
- required: true,
- },
-
- canCreatePipeline: {
- type: Boolean,
- required: true,
- },
- },
-};
+ };
</script>
<template>
<div class="nav-controls">
<a
- v-if="canCreatePipeline"
+ v-if="newPipelinePath"
:href="newPipelinePath"
- class="btn btn-create">
- Run Pipeline
- </a>
-
- <a
- v-if="!hasCiEnabled"
- :href="helpPagePath"
- class="btn btn-info">
- Get started with Pipelines
+ class="btn btn-create js-run-pipeline"
+ >
+ {{ s__('Pipelines|Run Pipeline') }}
</a>
<a
+ v-if="resetCachePath"
data-method="post"
- rel="nofollow"
:href="resetCachePath"
- class="btn btn-default">
- Clear runner caches
+ class="btn btn-default js-clear-cache"
+ >
+ {{ s__('Pipelines|Clear Runner Caches') }}
</a>
<a
+ v-if="ciLintPath"
:href="ciLintPath"
- class="btn btn-default">
- CI Lint
+ class="btn btn-default js-ci-lint"
+ >
+ {{ s__('Pipelines|CI Lint') }}
</a>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index 90930d5ff44..6e5ee68eeb1 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -1,12 +1,12 @@
<script>
import _ from 'underscore';
+ import { __, sprintf, s__ } from '../../locale';
import PipelinesService from '../services/pipelines_service';
import pipelinesMixin from '../mixins/pipelines';
- import tablePagination from '../../vue_shared/components/table_pagination.vue';
- import navigationTabs from '../../vue_shared/components/navigation_tabs.vue';
- import navigationControls from './nav_controls.vue';
+ import TablePagination from '../../vue_shared/components/table_pagination.vue';
+ import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
+ import NavigationControls from './nav_controls.vue';
import {
- convertPermissionToBoolean,
getParameterByName,
parseQueryStringIntoObject,
} from '../../lib/utils/common_utils';
@@ -14,9 +14,9 @@
export default {
components: {
- tablePagination,
- navigationTabs,
- navigationControls,
+ TablePagination,
+ NavigationTabs,
+ NavigationControls,
},
mixins: [
pipelinesMixin,
@@ -36,111 +36,186 @@
required: false,
default: 'root',
},
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ emptyStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ errorStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ noPipelinesSvgPath: {
+ type: String,
+ required: true,
+ },
+ autoDevopsPath: {
+ type: String,
+ required: true,
+ },
+ hasGitlabCi: {
+ type: Boolean,
+ required: true,
+ },
+ canCreatePipeline: {
+ type: Boolean,
+ required: true,
+ },
+ ciLintPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ resetCachePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ newPipelinePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
- const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
-
return {
- endpoint: pipelinesData.endpoint,
- helpPagePath: pipelinesData.helpPagePath,
- emptyStateSvgPath: pipelinesData.emptyStateSvgPath,
- errorStateSvgPath: pipelinesData.errorStateSvgPath,
- autoDevopsPath: pipelinesData.helpAutoDevopsPath,
- newPipelinePath: pipelinesData.newPipelinePath,
- canCreatePipeline: pipelinesData.canCreatePipeline,
- hasCi: pipelinesData.hasCi,
- ciLintPath: pipelinesData.ciLintPath,
- resetCachePath: pipelinesData.resetCachePath,
+ // Start with loading state to avoid a glitch when the empty state will be rendered
+ isLoading: true,
state: this.store.state,
scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1',
requestData: {},
};
},
- computed: {
- canCreatePipelineParsed() {
- return convertPermissionToBoolean(this.canCreatePipeline);
- },
+ stateMap: {
+ // with tabs
+ loading: 'loading',
+ tableList: 'tableList',
+ error: 'error',
+ emptyTab: 'emptyTab',
+ // without tabs
+ emptyState: 'emptyState',
+ },
+ scopes: {
+ all: 'all',
+ pending: 'pending',
+ running: 'running',
+ finished: 'finished',
+ branches: 'branches',
+ tags: 'tags',
+ },
+ computed: {
/**
- * The empty state should only be rendered when the request is made to fetch all pipelines
- * and none is returned.
- *
- * @return {Boolean}
- */
- shouldRenderEmptyState() {
- return !this.isLoading &&
- !this.hasError &&
- this.hasMadeRequest &&
- !this.state.pipelines.length &&
- (this.scope === 'all' || this.scope === null);
+ * `hasGitlabCi` handles both internal and external CI.
+ * The order on which the checks are made in this method is
+ * important to guarantee we handle all the corner cases.
+ */
+ stateToRender() {
+ const { stateMap } = this.$options;
+
+ if (this.isLoading) {
+ return stateMap.loading;
+ }
+
+ if (this.hasError) {
+ return stateMap.error;
+ }
+
+ if (this.state.pipelines.length) {
+ return stateMap.tableList;
+ }
+
+ if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
+ return stateMap.emptyTab;
+ }
+
+ return stateMap.emptyState;
},
/**
- * When a specific scope does not have pipelines we render a message.
- *
- * @return {Boolean}
+ * Tabs are rendered in all states except empty state.
+ * They are not rendered before the first request to avoid a flicker on first load.
*/
- shouldRenderNoPipelinesMessage() {
- return !this.isLoading &&
- !this.hasError &&
- !this.state.pipelines.length &&
- this.scope !== 'all' &&
- this.scope !== null;
+ shouldRenderTabs() {
+ const { stateMap } = this.$options;
+ return this.hasMadeRequest &&
+ [
+ stateMap.loading,
+ stateMap.tableList,
+ stateMap.error,
+ stateMap.emptyTab,
+ ].includes(this.stateToRender);
},
- shouldRenderTable() {
- return !this.hasError &&
- !this.isLoading && this.state.pipelines.length;
+ shouldRenderButtons() {
+ return (this.newPipelinePath ||
+ this.resetCachePath ||
+ this.ciLintPath) && this.shouldRenderTabs;
},
- /**
- * Pagination should only be rendered when there is more than one page.
- *
- * @return {Boolean}
- */
+
shouldRenderPagination() {
return !this.isLoading &&
this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage;
},
- hasCiEnabled() {
- return this.hasCi !== undefined;
+
+ emptyTabMessage() {
+ const { scopes } = this.$options;
+ const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
+
+ if (possibleScopes.includes(this.scope)) {
+ return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
+ scope: this.scope,
+ });
+ }
+
+ return s__('Pipelines|There are currently no pipelines.');
},
tabs() {
const { count } = this.state;
+ const { scopes } = this.$options;
+
return [
{
- name: 'All',
- scope: 'all',
+ name: __('All'),
+ scope: scopes.all,
count: count.all,
isActive: this.scope === 'all',
},
{
- name: 'Pending',
- scope: 'pending',
+ name: __('Pending'),
+ scope: scopes.pending,
count: count.pending,
isActive: this.scope === 'pending',
},
{
- name: 'Running',
- scope: 'running',
+ name: __('Running'),
+ scope: scopes.running,
count: count.running,
isActive: this.scope === 'running',
},
{
- name: 'Finished',
- scope: 'finished',
+ name: __('Finished'),
+ scope: scopes.finished,
count: count.finished,
isActive: this.scope === 'finished',
},
{
- name: 'Branches',
- scope: 'branches',
+ name: __('Branches'),
+ scope: scopes.branches,
isActive: this.scope === 'branches',
},
{
- name: 'Tags',
- scope: 'tags',
+ name: __('Tags'),
+ scope: scopes.tags,
isActive: this.scope === 'tags',
},
];
@@ -187,7 +262,7 @@
this.errorCallback();
// restart polling
- this.poll.restart();
+ this.poll.restart({ data: this.requestData });
});
},
},
@@ -197,69 +272,70 @@
<div class="pipelines-container">
<div
class="top-area scrolling-tabs-container inner-page-scroll-tabs"
- v-if="!shouldRenderEmptyState"
+ v-if="shouldRenderTabs || shouldRenderButtons"
>
<div class="fade-left">
<i
class="fa fa-angle-left"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</div>
<div class="fade-right">
<i
class="fa fa-angle-right"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</div>
<navigation-tabs
+ v-if="shouldRenderTabs"
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="pipelines"
/>
<navigation-controls
+ v-if="shouldRenderButtons"
:new-pipeline-path="newPipelinePath"
- :has-ci-enabled="hasCiEnabled"
- :help-page-path="helpPagePath"
:reset-cache-path="resetCachePath"
:ci-lint-path="ciLintPath"
- :can-create-pipeline="canCreatePipelineParsed "
/>
</div>
<div class="content-list pipelines">
<loading-icon
- label="Loading Pipelines"
+ v-if="stateToRender === $options.stateMap.loading"
+ :label="s__('Pipelines|Loading Pipelines')"
size="3"
- v-if="isLoading"
class="prepend-top-20"
/>
<empty-state
- v-if="shouldRenderEmptyState"
+ v-else-if="stateToRender === $options.stateMap.emptyState"
:help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath"
+ :can-set-ci="canCreatePipeline"
/>
- <error-state
- v-if="shouldRenderErrorState"
- :error-state-svg-path="errorStateSvgPath"
+ <svg-blank-state
+ v-else-if="stateToRender === $options.stateMap.error"
+ :svg-path="errorStateSvgPath"
+ :message="s__(`Pipelines|There was an error fetching the pipelines.
+ Try again in a few moments or contact your support team.`)"
/>
- <div
- class="blank-state-row"
- v-if="shouldRenderNoPipelinesMessage"
- >
- <div class="blank-state-center">
- <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
- </div>
- </div>
+ <svg-blank-state
+ v-else-if="stateToRender === $options.stateMap.emptyTab"
+ :svg-path="noPipelinesSvgPath"
+ :message="emptyTabMessage"
+ />
<div
class="table-holder"
- v-if="shouldRenderTable"
+ v-else-if="stateToRender === $options.stateMap.tableList"
>
<pipelines-table-component
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 50bdf80c3e3..9fcc07abee5 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -1,23 +1,19 @@
import Visibility from 'visibilityjs';
+import { __ } from '../../locale';
import Flash from '../../flash';
import Poll from '../../lib/utils/poll';
-import emptyState from '../components/empty_state.vue';
-import errorState from '../components/error_state.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import pipelinesTableComponent from '../components/pipelines_table.vue';
+import EmptyState from '../components/empty_state.vue';
+import SvgBlankState from '../components/blank_state.vue';
+import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
+import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub';
export default {
components: {
- pipelinesTableComponent,
- errorState,
- emptyState,
- loadingIcon,
- },
- computed: {
- shouldRenderErrorState() {
- return this.hasError && !this.isLoading;
- },
+ PipelinesTableComponent,
+ SvgBlankState,
+ EmptyState,
+ LoadingIcon,
},
data() {
return {
@@ -85,6 +81,7 @@ export default {
this.hasError = true;
this.isLoading = false;
this.updateGraphDropdown = false;
+ this.hasMadeRequest = true;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
@@ -96,7 +93,7 @@ export default {
postAction(endpoint) {
this.service.postAction(endpoint)
.then(() => eventHub.$emit('refreshPipelines'))
- .catch(() => new Flash('An error occurred while making the request.'));
+ .catch(() => Flash(__('An error occurred while making the request.')));
},
},
};
diff --git a/app/assets/javascripts/terminal/terminal_bundle.js b/app/assets/javascripts/terminal/index.js
index 134522ef961..1a75e072c4e 100644
--- a/app/assets/javascripts/terminal/terminal_bundle.js
+++ b/app/assets/javascripts/terminal/index.js
@@ -6,4 +6,4 @@ import './terminal';
window.Terminal = Terminal;
-$(() => new gl.Terminal({ selector: '#terminal' }));
+export default () => new gl.Terminal({ selector: '#terminal' });
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
new file mode 100644
index 00000000000..3b17135f0e5
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
@@ -0,0 +1,149 @@
+<script>
+import LabelsSelect from '~/labels_select';
+import LoadingIcon from '../../loading_icon.vue';
+
+import DropdownTitle from './dropdown_title.vue';
+import DropdownValue from './dropdown_value.vue';
+import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
+import DropdownButton from './dropdown_button.vue';
+import DropdownHiddenInput from './dropdown_hidden_input.vue';
+import DropdownHeader from './dropdown_header.vue';
+import DropdownSearchInput from './dropdown_search_input.vue';
+import DropdownFooter from './dropdown_footer.vue';
+import DropdownCreateLabel from './dropdown_create_label.vue';
+
+export default {
+ components: {
+ LoadingIcon,
+ DropdownTitle,
+ DropdownValue,
+ DropdownValueCollapsed,
+ DropdownButton,
+ DropdownHiddenInput,
+ DropdownHeader,
+ DropdownSearchInput,
+ DropdownFooter,
+ DropdownCreateLabel,
+ },
+ props: {
+ showCreate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ abilityName: {
+ type: String,
+ required: true,
+ },
+ context: {
+ type: Object,
+ required: true,
+ },
+ namespace: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ labelsPath: {
+ type: String,
+ required: true,
+ },
+ labelsWebUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ labelFilterBasePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ canEdit: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ hiddenInputName() {
+ return this.showCreate ? `${this.abilityName}[label_names][]` : 'label_id[]';
+ },
+ },
+ mounted() {
+ this.labelsDropdown = new LabelsSelect(this.$refs.dropdownButton, {
+ handleClick: this.handleClick,
+ });
+ },
+ methods: {
+ handleClick(label) {
+ this.$emit('onLabelClick', label);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="block labels">
+ <dropdown-value-collapsed
+ v-if="showCreate"
+ :labels="context.labels"
+ />
+ <dropdown-title
+ :can-edit="canEdit"
+ />
+ <dropdown-value
+ :labels="context.labels"
+ :label-filter-base-path="labelFilterBasePath"
+ >
+ <slot></slot>
+ </dropdown-value>
+ <div
+ v-if="canEdit"
+ class="selectbox"
+ style="display: none;"
+ >
+ <dropdown-hidden-input
+ v-for="label in context.labels"
+ :key="label.id"
+ :name="hiddenInputName"
+ :label="label"
+ />
+ <div class="dropdown">
+ <dropdown-button
+ :ability-name="abilityName"
+ :field-name="hiddenInputName"
+ :update-path="updatePath"
+ :labels-path="labelsPath"
+ :namespace="namespace"
+ :labels="context.labels"
+ :show-extra-options="!showCreate"
+ />
+ <div
+ class="dropdown-menu dropdown-select dropdown-menu-paging
+dropdown-menu-labels dropdown-menu-selectable"
+ >
+ <div class="dropdown-page-one">
+ <dropdown-header v-if="showCreate" />
+ <dropdown-search-input/>
+ <div class="dropdown-content"></div>
+ <div class="dropdown-loading">
+ <loading-icon />
+ </div>
+ <dropdown-footer
+ v-if="showCreate"
+ :labels-web-url="labelsWebUrl"
+ />
+ </div>
+ <dropdown-create-label
+ v-if="showCreate"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
new file mode 100644
index 00000000000..47497c1de98
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
@@ -0,0 +1,78 @@
+<script>
+import { __, s__, sprintf } from '~/locale';
+
+export default {
+ props: {
+ abilityName: {
+ type: String,
+ required: true,
+ },
+ fieldName: {
+ type: String,
+ required: true,
+ },
+ updatePath: {
+ type: String,
+ required: true,
+ },
+ labelsPath: {
+ type: String,
+ required: true,
+ },
+ namespace: {
+ type: String,
+ required: true,
+ },
+ labels: {
+ type: Array,
+ required: true,
+ },
+ showExtraOptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ dropdownToggleText() {
+ if (this.labels.length === 0) {
+ return __('Label');
+ }
+
+ if (this.labels.length > 1) {
+ return sprintf(s__('LabelSelect|%{firstLabelName} +%{remainingLabelCount} more'), {
+ firstLabelName: this.labels[0].title,
+ remainingLabelCount: this.labels.length - 1,
+ });
+ }
+
+ return this.labels[0].title;
+ },
+ },
+};
+</script>
+
+<template>
+ <button
+ type="button"
+ ref="dropdownButton"
+ class="dropdown-menu-toggle wide js-label-select js-multiselect js-context-config-modal"
+ data-toggle="dropdown"
+ :class="{ 'js-extra-options': showExtraOptions }"
+ :data-ability-name="abilityName"
+ :data-field-name="fieldName"
+ :data-issue-update="updatePath"
+ :data-labels="labelsPath"
+ :data-namespace-path="namespace"
+ :data-show-any="showExtraOptions"
+ >
+ <span class="dropdown-toggle-text">
+ {{ dropdownToggleText }}
+ </span>
+ <i
+ aria-hidden="true"
+ class="fa fa-chevron-down"
+ data-hidden="true"
+ >
+ </i>
+ </button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
new file mode 100644
index 00000000000..4200d1e8473
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
@@ -0,0 +1,84 @@
+<script>
+export default {
+ created() {
+ this.suggestedColors = gon.suggested_label_colors;
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown-page-two dropdown-new-label">
+ <div class="dropdown-title">
+ <button
+ type="button"
+ class="dropdown-title-button dropdown-menu-back"
+ :aria-label="__('Go back')"
+ >
+ <i
+ aria-hidden="true"
+ class="fa fa-arrow-left"
+ data-hidden="true"
+ >
+ </i>
+ </button>
+ {{ __('Create new label') }}
+ <button
+ type="button"
+ class="dropdown-title-button dropdown-menu-close"
+ :aria-label="__('Close')"
+ >
+ <i
+ aria-hidden="true"
+ class="fa fa-times dropdown-menu-close-icon"
+ data-hidden="true"
+ >
+ </i>
+ </button>
+ </div>
+ <div class="dropdown-content">
+ <div class="dropdown-labels-error js-label-error"></div>
+ <input
+ id="new_label_name"
+ type="text"
+ class="default-dropdown-input"
+ :placeholder="__('Name new label')"
+ />
+ <div class="suggest-colors suggest-colors-dropdown">
+ <a
+ v-for="(color, index) in suggestedColors"
+ href="#"
+ :key="index"
+ :data-color="color"
+ :style="{
+ backgroundColor: color,
+ }"
+ >
+ &nbsp;
+ </a>
+ </div>
+ <div class="dropdown-label-color-input">
+ <div class="dropdown-label-color-preview js-dropdown-label-color-preview"></div>
+ <input
+ id="new_label_color"
+ type="text"
+ class="default-dropdown-input"
+ :placeholder="__('Assign custom color like #FF0000')"
+ />
+ </div>
+ <div class="clearfix">
+ <button
+ type="button"
+ class="btn btn-primary pull-left js-new-label-btn disabled"
+ >
+ {{ __('Create') }}
+ </button>
+ <button
+ type="button"
+ class="btn btn-default pull-right js-cancel-label-btn"
+ >
+ {{ __('Cancel') }}
+ </button>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue
new file mode 100644
index 00000000000..e951a863811
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue
@@ -0,0 +1,34 @@
+<script>
+export default {
+ props: {
+ labelsWebUrl: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown-footer">
+ <ul class="dropdown-footer-list">
+ <li>
+ <a
+ href="#"
+ class="dropdown-toggle-page"
+ >
+ {{ __('Create new label') }}
+ </a>
+ </li>
+ <li>
+ <a
+ data-is-link="true"
+ class="dropdown-external-link"
+ :href="labelsWebUrl"
+ >
+ {{ __('Manage labels') }}
+ </a>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue
new file mode 100644
index 00000000000..7664acdf19c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue
@@ -0,0 +1,21 @@
+<script>
+export default {};
+</script>
+
+<template>
+ <div class="dropdown-title">
+ <span>{{ __('Assign labels') }}</span>
+ <button
+ type="button"
+ class="dropdown-title-button dropdown-menu-close"
+ :aria-label="__('Close')"
+ >
+ <i
+ aria-hidden="true"
+ class="fa fa-times dropdown-menu-close-icon"
+ data-hidden="true"
+ >
+ </i>
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue
new file mode 100644
index 00000000000..1832c3c1757
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue
@@ -0,0 +1,22 @@
+<script>
+export default {
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <input
+ type="hidden"
+ :name="name"
+ :value="label.id"
+ />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue
new file mode 100644
index 00000000000..ae633460c95
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue
@@ -0,0 +1,27 @@
+<script>
+export default {};
+</script>
+
+<template>
+ <div class="dropdown-input">
+ <input
+ autocomplete="off"
+ class="dropdown-input-field"
+ type="search"
+ :placeholder="__('Search')"
+ />
+ <i
+ aria-hidden="true"
+ class="fa fa-search dropdown-input-search"
+ data-hidden="true"
+ >
+ </i>
+ <i
+ aria-hidden="true"
+ class="fa fa-times dropdown-input-clear js-dropdown-input-clear"
+ data-hidden="true"
+ role="button"
+ >
+ </i>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue
new file mode 100644
index 00000000000..7da82e90e29
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue
@@ -0,0 +1,30 @@
+<script>
+export default {
+ props: {
+ canEdit: {
+ type: Boolean,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="title hide-collapsed append-bottom-10">
+ {{ __('Labels') }}
+ <template v-if="canEdit">
+ <i
+ aria-hidden="true"
+ class="fa fa-spinner fa-spin block-loading"
+ data-hidden="true"
+ >
+ </i>
+ <button
+ type="button"
+ class="edit-link btn btn-blank pull-right js-sidebar-dropdown-toggle"
+ >
+ {{ __('Edit') }}
+ </button>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue
new file mode 100644
index 00000000000..ba4c8fba5ec
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue
@@ -0,0 +1,63 @@
+<script>
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ labels: {
+ type: Array,
+ required: true,
+ },
+ labelFilterBasePath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ isEmpty() {
+ return this.labels.length === 0;
+ },
+ },
+ methods: {
+ labelFilterUrl(label) {
+ return `${this.labelFilterBasePath}?label_name[]=${encodeURIComponent(label.title)}`;
+ },
+ labelStyle(label) {
+ return {
+ color: label.textColor,
+ backgroundColor: label.color,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="hide-collapsed value issuable-show-labels">
+ <span
+ v-if="isEmpty"
+ class="text-secondary"
+ >
+ <slot>{{ __('None') }}</slot>
+ </span>
+ <a
+ v-else
+ v-for="label in labels"
+ :key="label.id"
+ :href="labelFilterUrl(label)"
+ >
+ <span
+ v-tooltip
+ class="label color-label"
+ data-placement="bottom"
+ data-container="body"
+ :style="labelStyle(label)"
+ :title="label.description"
+ >
+ {{ label.title }}
+ </span>
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
new file mode 100644
index 00000000000..5cf728fe050
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
@@ -0,0 +1,48 @@
+<script>
+import { s__, sprintf } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ labels: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ labelsList() {
+ const labelsString = this.labels.slice(0, 5).map(label => label.title).join(', ');
+
+ if (this.labels.length > 5) {
+ return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), {
+ labelsString,
+ remainingLabelCount: this.labels.length - 5,
+ });
+ }
+
+ return labelsString;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ v-tooltip
+ class="sidebar-collapsed-icon"
+ data-placement="left"
+ data-container="body"
+ :title="labelsList"
+ >
+ <i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-tags"
+ >
+ </i>
+ <span>{{ labels.length }}</span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/vue_shared/models/label.js
index 98c1ec014c4..70b9efe0c68 100644
--- a/app/assets/javascripts/boards/models/label.js
+++ b/app/assets/javascripts/vue_shared/models/label.js
@@ -1,7 +1,5 @@
-/* eslint-disable no-unused-vars, space-before-function-paren */
-
class ListLabel {
- constructor (obj) {
+ constructor(obj) {
this.id = obj.id;
this.title = obj.title;
this.type = obj.type;