From 213e91d43926f09eb969859aa2c306eeb127deb4 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Thu, 21 Dec 2017 15:05:47 +0000 Subject: Resolve "Decouple multi-file editor from file list" --- app/assets/javascripts/api.js | 14 +- app/assets/javascripts/dispatcher.js | 5 - app/assets/javascripts/fly_out_nav.js | 17 +- .../javascripts/helpers/user_feature_helper.js | 7 - .../ide/components/commit_sidebar/list.vue | 66 ++++++++ .../components/commit_sidebar/list_collapsed.vue | 35 ++++ .../ide/components/commit_sidebar/list_item.vue | 36 ++++ app/assets/javascripts/ide/components/ide.vue | 73 ++++++++ .../javascripts/ide/components/ide_context_bar.vue | 75 ++++++++ .../ide/components/ide_project_branches_tree.vue | 47 ++++++ .../ide/components/ide_project_tree.vue | 47 ++++++ .../javascripts/ide/components/ide_repo_tree.vue | 66 ++++++++ .../javascripts/ide/components/ide_side_bar.vue | 62 +++++++ .../javascripts/ide/components/ide_status_bar.vue | 71 ++++++++ .../javascripts/ide/components/new_branch_form.vue | 108 ++++++++++++ .../ide/components/new_dropdown/index.vue | 101 +++++++++++ .../ide/components/new_dropdown/modal.vue | 112 ++++++++++++ .../ide/components/new_dropdown/upload.vue | 87 ++++++++++ .../ide/components/repo_commit_section.vue | 171 +++++++++++++++++++ .../ide/components/repo_edit_button.vue | 57 +++++++ .../javascripts/ide/components/repo_editor.vue | 127 ++++++++++++++ .../javascripts/ide/components/repo_file.vue | 165 ++++++++++++++++++ .../ide/components/repo_file_buttons.vue | 56 ++++++ .../ide/components/repo_loading_file.vue | 44 +++++ .../ide/components/repo_prev_directory.vue | 32 ++++ .../javascripts/ide/components/repo_preview.vue | 65 +++++++ app/assets/javascripts/ide/components/repo_tab.vue | 69 ++++++++ .../javascripts/ide/components/repo_tabs.vue | 27 +++ app/assets/javascripts/ide/ide_router.js | 101 +++++++++++ app/assets/javascripts/ide/index.js | 55 ++++++ .../javascripts/ide/lib/common/disposable.js | 14 ++ app/assets/javascripts/ide/lib/common/model.js | 64 +++++++ .../javascripts/ide/lib/common/model_manager.js | 32 ++++ .../javascripts/ide/lib/decorations/controller.js | 43 +++++ app/assets/javascripts/ide/lib/diff/controller.js | 71 ++++++++ app/assets/javascripts/ide/lib/diff/diff.js | 30 ++++ app/assets/javascripts/ide/lib/diff/diff_worker.js | 10 ++ app/assets/javascripts/ide/lib/editor.js | 109 ++++++++++++ app/assets/javascripts/ide/lib/editor_options.js | 2 + app/assets/javascripts/ide/monaco_loader.js | 11 ++ app/assets/javascripts/ide/services/index.js | 47 ++++++ app/assets/javascripts/ide/stores/actions.js | 179 ++++++++++++++++++++ .../javascripts/ide/stores/actions/branch.js | 43 +++++ app/assets/javascripts/ide/stores/actions/file.js | 131 ++++++++++++++ .../javascripts/ide/stores/actions/project.js | 25 +++ app/assets/javascripts/ide/stores/actions/tree.js | 188 +++++++++++++++++++++ app/assets/javascripts/ide/stores/getters.js | 19 +++ app/assets/javascripts/ide/stores/index.js | 15 ++ .../javascripts/ide/stores/mutation_types.js | 45 +++++ app/assets/javascripts/ide/stores/mutations.js | 65 +++++++ .../javascripts/ide/stores/mutations/branch.js | 28 +++ .../javascripts/ide/stores/mutations/file.js | 74 ++++++++ .../javascripts/ide/stores/mutations/project.js | 23 +++ .../javascripts/ide/stores/mutations/tree.js | 36 ++++ app/assets/javascripts/ide/stores/state.js | 22 +++ app/assets/javascripts/ide/stores/utils.js | 175 +++++++++++++++++++ app/assets/javascripts/new_commit_form.js | 7 +- .../repo/components/commit_sidebar/list.vue | 89 ---------- .../components/commit_sidebar/list_collapsed.vue | 35 ---- .../repo/components/commit_sidebar/list_item.vue | 36 ---- .../repo/components/new_branch_form.vue | 108 ------------ .../repo/components/new_dropdown/index.vue | 89 ---------- .../repo/components/new_dropdown/modal.vue | 98 ----------- .../repo/components/new_dropdown/upload.vue | 68 -------- app/assets/javascripts/repo/components/repo.vue | 63 ------- .../repo/components/repo_commit_section.vue | 178 ------------------- .../repo/components/repo_edit_button.vue | 57 ------- .../javascripts/repo/components/repo_editor.vue | 89 ---------- .../javascripts/repo/components/repo_file.vue | 117 ------------- .../repo/components/repo_file_buttons.vue | 56 ------ .../repo/components/repo_loading_file.vue | 44 ----- .../repo/components/repo_prev_directory.vue | 34 ---- .../javascripts/repo/components/repo_preview.vue | 65 ------- .../javascripts/repo/components/repo_sidebar.vue | 85 ---------- .../javascripts/repo/components/repo_tab.vue | 67 -------- .../javascripts/repo/components/repo_tabs.vue | 27 --- app/assets/javascripts/repo/index.js | 106 ------------ .../javascripts/repo/lib/common/disposable.js | 14 -- app/assets/javascripts/repo/lib/common/model.js | 56 ------ .../javascripts/repo/lib/common/model_manager.js | 32 ---- .../javascripts/repo/lib/decorations/controller.js | 43 ----- app/assets/javascripts/repo/lib/diff/controller.js | 71 -------- app/assets/javascripts/repo/lib/diff/diff.js | 30 ---- .../javascripts/repo/lib/diff/diff_worker.js | 10 -- app/assets/javascripts/repo/lib/editor.js | 79 --------- app/assets/javascripts/repo/lib/editor_options.js | 2 - app/assets/javascripts/repo/monaco_loader.js | 11 -- app/assets/javascripts/repo/services/index.js | 44 ----- app/assets/javascripts/repo/stores/actions.js | 146 ---------------- .../javascripts/repo/stores/actions/branch.js | 20 --- app/assets/javascripts/repo/stores/actions/file.js | 110 ------------ app/assets/javascripts/repo/stores/actions/tree.js | 163 ------------------ app/assets/javascripts/repo/stores/getters.js | 40 ----- app/assets/javascripts/repo/stores/index.js | 15 -- .../javascripts/repo/stores/mutation_types.js | 30 ---- app/assets/javascripts/repo/stores/mutations.js | 61 ------- .../javascripts/repo/stores/mutations/branch.js | 9 - .../javascripts/repo/stores/mutations/file.js | 54 ------ .../javascripts/repo/stores/mutations/tree.js | 27 --- app/assets/javascripts/repo/stores/state.js | 24 --- app/assets/javascripts/repo/stores/utils.js | 127 -------------- .../vue_shared/components/project_avatar/image.vue | 103 +++++++++++ 102 files changed, 3585 insertions(+), 2753 deletions(-) delete mode 100644 app/assets/javascripts/helpers/user_feature_helper.js create mode 100644 app/assets/javascripts/ide/components/commit_sidebar/list.vue create mode 100644 app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue create mode 100644 app/assets/javascripts/ide/components/commit_sidebar/list_item.vue create mode 100644 app/assets/javascripts/ide/components/ide.vue create mode 100644 app/assets/javascripts/ide/components/ide_context_bar.vue create mode 100644 app/assets/javascripts/ide/components/ide_project_branches_tree.vue create mode 100644 app/assets/javascripts/ide/components/ide_project_tree.vue create mode 100644 app/assets/javascripts/ide/components/ide_repo_tree.vue create mode 100644 app/assets/javascripts/ide/components/ide_side_bar.vue create mode 100644 app/assets/javascripts/ide/components/ide_status_bar.vue create mode 100644 app/assets/javascripts/ide/components/new_branch_form.vue create mode 100644 app/assets/javascripts/ide/components/new_dropdown/index.vue create mode 100644 app/assets/javascripts/ide/components/new_dropdown/modal.vue create mode 100644 app/assets/javascripts/ide/components/new_dropdown/upload.vue create mode 100644 app/assets/javascripts/ide/components/repo_commit_section.vue create mode 100644 app/assets/javascripts/ide/components/repo_edit_button.vue create mode 100644 app/assets/javascripts/ide/components/repo_editor.vue create mode 100644 app/assets/javascripts/ide/components/repo_file.vue create mode 100644 app/assets/javascripts/ide/components/repo_file_buttons.vue create mode 100644 app/assets/javascripts/ide/components/repo_loading_file.vue create mode 100644 app/assets/javascripts/ide/components/repo_prev_directory.vue create mode 100644 app/assets/javascripts/ide/components/repo_preview.vue create mode 100644 app/assets/javascripts/ide/components/repo_tab.vue create mode 100644 app/assets/javascripts/ide/components/repo_tabs.vue create mode 100644 app/assets/javascripts/ide/ide_router.js create mode 100644 app/assets/javascripts/ide/index.js create mode 100644 app/assets/javascripts/ide/lib/common/disposable.js create mode 100644 app/assets/javascripts/ide/lib/common/model.js create mode 100644 app/assets/javascripts/ide/lib/common/model_manager.js create mode 100644 app/assets/javascripts/ide/lib/decorations/controller.js create mode 100644 app/assets/javascripts/ide/lib/diff/controller.js create mode 100644 app/assets/javascripts/ide/lib/diff/diff.js create mode 100644 app/assets/javascripts/ide/lib/diff/diff_worker.js create mode 100644 app/assets/javascripts/ide/lib/editor.js create mode 100644 app/assets/javascripts/ide/lib/editor_options.js create mode 100644 app/assets/javascripts/ide/monaco_loader.js create mode 100644 app/assets/javascripts/ide/services/index.js create mode 100644 app/assets/javascripts/ide/stores/actions.js create mode 100644 app/assets/javascripts/ide/stores/actions/branch.js create mode 100644 app/assets/javascripts/ide/stores/actions/file.js create mode 100644 app/assets/javascripts/ide/stores/actions/project.js create mode 100644 app/assets/javascripts/ide/stores/actions/tree.js create mode 100644 app/assets/javascripts/ide/stores/getters.js create mode 100644 app/assets/javascripts/ide/stores/index.js create mode 100644 app/assets/javascripts/ide/stores/mutation_types.js create mode 100644 app/assets/javascripts/ide/stores/mutations.js create mode 100644 app/assets/javascripts/ide/stores/mutations/branch.js create mode 100644 app/assets/javascripts/ide/stores/mutations/file.js create mode 100644 app/assets/javascripts/ide/stores/mutations/project.js create mode 100644 app/assets/javascripts/ide/stores/mutations/tree.js create mode 100644 app/assets/javascripts/ide/stores/state.js create mode 100644 app/assets/javascripts/ide/stores/utils.js delete mode 100644 app/assets/javascripts/repo/components/commit_sidebar/list.vue delete mode 100644 app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue delete mode 100644 app/assets/javascripts/repo/components/commit_sidebar/list_item.vue delete mode 100644 app/assets/javascripts/repo/components/new_branch_form.vue delete mode 100644 app/assets/javascripts/repo/components/new_dropdown/index.vue delete mode 100644 app/assets/javascripts/repo/components/new_dropdown/modal.vue delete mode 100644 app/assets/javascripts/repo/components/new_dropdown/upload.vue delete mode 100644 app/assets/javascripts/repo/components/repo.vue delete mode 100644 app/assets/javascripts/repo/components/repo_commit_section.vue delete mode 100644 app/assets/javascripts/repo/components/repo_edit_button.vue delete mode 100644 app/assets/javascripts/repo/components/repo_editor.vue delete mode 100644 app/assets/javascripts/repo/components/repo_file.vue delete mode 100644 app/assets/javascripts/repo/components/repo_file_buttons.vue delete mode 100644 app/assets/javascripts/repo/components/repo_loading_file.vue delete mode 100644 app/assets/javascripts/repo/components/repo_prev_directory.vue delete mode 100644 app/assets/javascripts/repo/components/repo_preview.vue delete mode 100644 app/assets/javascripts/repo/components/repo_sidebar.vue delete mode 100644 app/assets/javascripts/repo/components/repo_tab.vue delete mode 100644 app/assets/javascripts/repo/components/repo_tabs.vue delete mode 100644 app/assets/javascripts/repo/index.js delete mode 100644 app/assets/javascripts/repo/lib/common/disposable.js delete mode 100644 app/assets/javascripts/repo/lib/common/model.js delete mode 100644 app/assets/javascripts/repo/lib/common/model_manager.js delete mode 100644 app/assets/javascripts/repo/lib/decorations/controller.js delete mode 100644 app/assets/javascripts/repo/lib/diff/controller.js delete mode 100644 app/assets/javascripts/repo/lib/diff/diff.js delete mode 100644 app/assets/javascripts/repo/lib/diff/diff_worker.js delete mode 100644 app/assets/javascripts/repo/lib/editor.js delete mode 100644 app/assets/javascripts/repo/lib/editor_options.js delete mode 100644 app/assets/javascripts/repo/monaco_loader.js delete mode 100644 app/assets/javascripts/repo/services/index.js delete mode 100644 app/assets/javascripts/repo/stores/actions.js delete mode 100644 app/assets/javascripts/repo/stores/actions/branch.js delete mode 100644 app/assets/javascripts/repo/stores/actions/file.js delete mode 100644 app/assets/javascripts/repo/stores/actions/tree.js delete mode 100644 app/assets/javascripts/repo/stores/getters.js delete mode 100644 app/assets/javascripts/repo/stores/index.js delete mode 100644 app/assets/javascripts/repo/stores/mutation_types.js delete mode 100644 app/assets/javascripts/repo/stores/mutations.js delete mode 100644 app/assets/javascripts/repo/stores/mutations/branch.js delete mode 100644 app/assets/javascripts/repo/stores/mutations/file.js delete mode 100644 app/assets/javascripts/repo/stores/mutations/tree.js delete mode 100644 app/assets/javascripts/repo/stores/state.js delete mode 100644 app/assets/javascripts/repo/stores/utils.js create mode 100644 app/assets/javascripts/vue_shared/components/project_avatar/image.vue (limited to 'app/assets/javascripts') diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index d963101028a..21d8c790e90 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import axios from './lib/utils/axios_utils'; const Api = { groupsPath: '/api/:version/groups.json', @@ -6,6 +7,7 @@ const Api = { namespacesPath: '/api/:version/namespaces.json', groupProjectsPath: '/api/:version/groups/:id/projects.json', projectsPath: '/api/:version/projects.json', + projectPath: '/api/:version/projects/:id', projectLabelsPath: '/:namespace_path/:project_path/labels', groupLabelsPath: '/groups/:namespace_path/labels', licensePath: '/api/:version/templates/licenses/:key', @@ -76,6 +78,14 @@ const Api = { .done(projects => callback(projects)); }, + // Return single project + project(projectPath) { + const url = Api.buildUrl(Api.projectPath) + .replace(':id', encodeURIComponent(projectPath)); + + return axios.get(url); + }, + newLabel(namespacePath, projectPath, data, callback) { let url; @@ -115,7 +125,7 @@ const Api = { commitMultiple(id, data) { // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions const url = Api.buildUrl(Api.commitPath) - .replace(':id', id); + .replace(':id', encodeURIComponent(id)); return this.wrapAjaxCall({ url, type: 'POST', @@ -127,7 +137,7 @@ const Api = { branchSingle(id, branch) { const url = Api.buildUrl(Api.branchSinglePath) - .replace(':id', id) + .replace(':id', encodeURIComponent(id)) .replace(':branch', branch); return this.wrapAjaxCall({ diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 62867c56214..07df3c216b1 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -73,7 +73,6 @@ import initLegacyFilters from './init_legacy_filters'; import initIssuableSidebar from './init_issuable_sidebar'; import initProjectVisibilitySelector from './project_visibility'; import GpgBadges from './gpg_badges'; -import UserFeatureHelper from './helpers/user_feature_helper'; import initChangesDropdown from './init_changes_dropdown'; import NewGroupChild from './groups/new_group_child'; import AbuseReports from './abuse_reports'; @@ -447,9 +446,6 @@ import Activities from './activities'; break; case 'projects:tree:show': shortcut_handler = new ShortcutsNavigation(); - - if (UserFeatureHelper.isNewRepoEnabled()) break; - new TreeView(); new BlobViewer(); new NewCommitForm($('.js-create-dir-form')); @@ -468,7 +464,6 @@ import Activities from './activities'; shortcut_handler = true; break; case 'projects:blob:show': - if (UserFeatureHelper.isNewRepoEnabled()) break; new BlobViewer(); initBlob(); break; diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index 6110d961609..abb04d77f8f 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -161,13 +161,16 @@ export default () => { const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')]; - sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => { - clearTimeout(timeoutId); - - timeoutId = setTimeout(() => { - if (currentOpenMenu) hideMenu(currentOpenMenu); - }, getHideSubItemsInterval()); - }); + const topItems = sidebar.querySelector('.sidebar-top-level-items'); + if (topItems) { + sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => { + clearTimeout(timeoutId); + + timeoutId = setTimeout(() => { + if (currentOpenMenu) hideMenu(currentOpenMenu); + }, getHideSubItemsInterval()); + }); + } headerHeight = document.querySelector('.nav-sidebar').offsetTop; diff --git a/app/assets/javascripts/helpers/user_feature_helper.js b/app/assets/javascripts/helpers/user_feature_helper.js deleted file mode 100644 index 638118a5204..00000000000 --- a/app/assets/javascripts/helpers/user_feature_helper.js +++ /dev/null @@ -1,7 +0,0 @@ -import Cookies from 'js-cookie'; - -export default { - isNewRepoEnabled() { - return Cookies.get('new_repo') === 'true'; - }, -}; diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue new file mode 100644 index 00000000000..704dff981df --- /dev/null +++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue @@ -0,0 +1,66 @@ + + + diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue new file mode 100644 index 00000000000..6a0262f271b --- /dev/null +++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue @@ -0,0 +1,35 @@ + + + diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue new file mode 100644 index 00000000000..742f746e02f --- /dev/null +++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue @@ -0,0 +1,36 @@ + + + diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue new file mode 100644 index 00000000000..7f29a355eca --- /dev/null +++ b/app/assets/javascripts/ide/components/ide.vue @@ -0,0 +1,73 @@ + + + diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue new file mode 100644 index 00000000000..5a08718e386 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_context_bar.vue @@ -0,0 +1,75 @@ + + + diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue new file mode 100644 index 00000000000..bd3a521ff43 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue @@ -0,0 +1,47 @@ + + + diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue new file mode 100644 index 00000000000..61daba6d176 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_project_tree.vue @@ -0,0 +1,47 @@ + + + diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue new file mode 100644 index 00000000000..b6b089e6b25 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_repo_tree.vue @@ -0,0 +1,66 @@ + + + diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue new file mode 100644 index 00000000000..535398d98c2 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -0,0 +1,62 @@ + + + diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue new file mode 100644 index 00000000000..a24abadd936 --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -0,0 +1,71 @@ + + + diff --git a/app/assets/javascripts/ide/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue new file mode 100644 index 00000000000..2119d373d31 --- /dev/null +++ b/app/assets/javascripts/ide/components/new_branch_form.vue @@ -0,0 +1,108 @@ + + + diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue new file mode 100644 index 00000000000..6e67e99a70f --- /dev/null +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -0,0 +1,101 @@ + + + diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue new file mode 100644 index 00000000000..a0650d37690 --- /dev/null +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -0,0 +1,112 @@ + + + diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue new file mode 100644 index 00000000000..2a2f2a241fc --- /dev/null +++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue @@ -0,0 +1,87 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue new file mode 100644 index 00000000000..470db2c9650 --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -0,0 +1,171 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue new file mode 100644 index 00000000000..37bd9003e96 --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_edit_button.vue @@ -0,0 +1,57 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue new file mode 100644 index 00000000000..221be4b9074 --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -0,0 +1,127 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue new file mode 100644 index 00000000000..09ca11531b1 --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -0,0 +1,165 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue new file mode 100644 index 00000000000..34f0d51819a --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_file_buttons.vue @@ -0,0 +1,56 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue new file mode 100644 index 00000000000..7eb840c7608 --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_loading_file.vue @@ -0,0 +1,44 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_prev_directory.vue b/app/assets/javascripts/ide/components/repo_prev_directory.vue new file mode 100644 index 00000000000..7cd359ea4ed --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_prev_directory.vue @@ -0,0 +1,32 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue new file mode 100644 index 00000000000..3d1e0297bd5 --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_preview.vue @@ -0,0 +1,65 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue new file mode 100644 index 00000000000..5bd63ac9ec5 --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_tab.vue @@ -0,0 +1,69 @@ + + + diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue new file mode 100644 index 00000000000..ab0bef4f0ac --- /dev/null +++ b/app/assets/javascripts/ide/components/repo_tabs.vue @@ -0,0 +1,27 @@ + + + diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js new file mode 100644 index 00000000000..a9cbf8e370f --- /dev/null +++ b/app/assets/javascripts/ide/ide_router.js @@ -0,0 +1,101 @@ +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.'); + throw e; + }); + } + }) + .catch((e) => { + flash('Error while loading the project data. Please try again.'); + throw e; + }); + } + + next(); +}); + +export default router; diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js new file mode 100644 index 00000000000..a96bd339f51 --- /dev/null +++ b/app/assets/javascripts/ide/index.js @@ -0,0 +1,55 @@ +import Vue from 'vue'; +import { mapActions } from 'vuex'; +import { convertPermissionToBoolean } from '../lib/utils/common_utils'; +import ide from './components/ide.vue'; + +import store from './stores'; +import router from './ide_router'; +import Translate from '../vue_shared/translate'; +import ContextualSidebar from '../contextual_sidebar'; + +function initIde(el) { + if (!el) return null; + + return new Vue({ + el, + store, + router, + components: { + ide, + }, + methods: { + ...mapActions([ + 'setInitialData', + ]), + }, + created() { + const data = el.dataset; + + this.setInitialData({ + endpoints: { + rootEndpoint: data.url, + newMergeRequestUrl: data.newMergeRequestUrl, + rootUrl: data.rootUrl, + }, + canCommit: convertPermissionToBoolean(data.canCommit), + onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch), + path: data.currentPath, + isRoot: convertPermissionToBoolean(data.root), + isInitialRoot: convertPermissionToBoolean(data.root), + }); + }, + render(createElement) { + return createElement('ide'); + }, + }); +} + +const ideElement = document.getElementById('ide'); + +Vue.use(Translate); + +initIde(ideElement); + +const contextualSidebar = new ContextualSidebar(); +contextualSidebar.bindEvents(); diff --git a/app/assets/javascripts/ide/lib/common/disposable.js b/app/assets/javascripts/ide/lib/common/disposable.js new file mode 100644 index 00000000000..84b29bdb600 --- /dev/null +++ b/app/assets/javascripts/ide/lib/common/disposable.js @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000000..14d9fe4771e --- /dev/null +++ b/app/assets/javascripts/ide/lib/common/model.js @@ -0,0 +1,64 @@ +/* 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 new file mode 100644 index 00000000000..fd462252795 --- /dev/null +++ b/app/assets/javascripts/ide/lib/common/model_manager.js @@ -0,0 +1,32 @@ +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 new file mode 100644 index 00000000000..0954b7973c4 --- /dev/null +++ b/app/assets/javascripts/ide/lib/decorations/controller.js @@ -0,0 +1,43 @@ +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 new file mode 100644 index 00000000000..dc0b1c95e59 --- /dev/null +++ b/app/assets/javascripts/ide/lib/diff/controller.js @@ -0,0 +1,71 @@ +/* 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 new file mode 100644 index 00000000000..0e37f5c4704 --- /dev/null +++ b/app/assets/javascripts/ide/lib/diff/diff.js @@ -0,0 +1,30 @@ +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 new file mode 100644 index 00000000000..e74c4046330 --- /dev/null +++ b/app/assets/javascripts/ide/lib/diff/diff_worker.js @@ -0,0 +1,10 @@ +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 new file mode 100644 index 00000000000..51e202b9348 --- /dev/null +++ b/app/assets/javascripts/ide/lib/editor.js @@ -0,0 +1,109 @@ +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()); + 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; + }, {})); + + 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 new file mode 100644 index 00000000000..701affc466e --- /dev/null +++ b/app/assets/javascripts/ide/lib/editor_options.js @@ -0,0 +1,2 @@ +export default [{ +}]; diff --git a/app/assets/javascripts/ide/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js new file mode 100644 index 00000000000..af83a1ec0b4 --- /dev/null +++ b/app/assets/javascripts/ide/monaco_loader.js @@ -0,0 +1,11 @@ +import monacoContext from 'monaco-editor/dev/vs/loader'; + +monacoContext.require.config({ + paths: { + vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase + }, +}); + +// 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 new file mode 100644 index 00000000000..1fb24e93f2e --- /dev/null +++ b/app/assets/javascripts/ide/services/index.js @@ -0,0 +1,47 @@ +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 new file mode 100644 index 00000000000..c01046c8c76 --- /dev/null +++ b/app/assets/javascripts/ide/stores/actions.js @@ -0,0 +1,179 @@ +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'; + +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 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.')); + +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); + 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, + }, + }; + + flash( + `Your changes have been committed. Commit ${data.short_id} with ${ + data.stats.additions + } additions, ${data.stats.deletions} deletions.`, + 'notice', + ); + + if (newMr) { + 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'); + dispatch('closeAllFiles'); + + window.scrollTo(0, 0); + } + }) + .catch(() => flash('Error committing changes. Please try again.')); + +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 new file mode 100644 index 00000000000..32bdf7fec22 --- /dev/null +++ b/app/assets/javascripts/ide/stores/actions/branch.js @@ -0,0 +1,43 @@ +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.'); + 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 new file mode 100644 index 00000000000..0f27d5bf1c3 --- /dev/null +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -0,0 +1,131 @@ +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.'); + }); +}; + +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.')); + +export const changeFileContent = ({ commit }, { file, content }) => { + commit(types.UPDATE_FILE_CONTENT, { file, content }); +}; + +export const setFileLanguage = ({ state, commit }, { fileLanguage }) => { + commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage }); +}; + +export const setFileEOL = ({ state, commit }, { eol }) => { + commit(types.SET_FILE_EOL, { file: state.selectedFile, eol }); +}; + +export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => { + 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.`); + + 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 new file mode 100644 index 00000000000..75e332090cb --- /dev/null +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -0,0 +1,25 @@ +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) { + service.getProjectData(namespace, projectId) + .then(res => res.data) + .then((data) => { + 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.'); + 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 new file mode 100644 index 00000000000..25909400a75 --- /dev/null +++ b/app/assets/javascripts/ide/stores/actions/tree.js @@ -0,0 +1,188 @@ +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.'); + 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.')); +}; + +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 new file mode 100644 index 00000000000..6b51ccff817 --- /dev/null +++ b/app/assets/javascripts/ide/stores/getters.js @@ -0,0 +1,19 @@ +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 new file mode 100644 index 00000000000..6ac9bfd8189 --- /dev/null +++ b/app/assets/javascripts/ide/stores/index.js @@ -0,0 +1,15 @@ +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 new file mode 100644 index 00000000000..4e3c10972ba --- /dev/null +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -0,0 +1,45 @@ +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'; + +// 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 new file mode 100644 index 00000000000..2fed9019cb6 --- /dev/null +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -0,0 +1,65 @@ +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_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 new file mode 100644 index 00000000000..04b9582c5bb --- /dev/null +++ b/app/assets/javascripts/ide/stores/mutations/branch.js @@ -0,0 +1,28 @@ +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 new file mode 100644 index 00000000000..5f3655b0092 --- /dev/null +++ b/app/assets/javascripts/ide/stores/mutations/file.js @@ -0,0 +1,74 @@ +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: '', + 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 new file mode 100644 index 00000000000..2816562a919 --- /dev/null +++ b/app/assets/javascripts/ide/stores/mutations/project.js @@ -0,0 +1,23 @@ +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 new file mode 100644 index 00000000000..4fe438ab465 --- /dev/null +++ b/app/assets/javascripts/ide/stores/mutations/tree.js @@ -0,0 +1,36 @@ +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 new file mode 100644 index 00000000000..539e382830f --- /dev/null +++ b/app/assets/javascripts/ide/stores/state.js @@ -0,0 +1,22 @@ +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, +}); diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js new file mode 100644 index 00000000000..29e3ab5d040 --- /dev/null +++ b/app/assets/javascripts/ide/stores/utils.js @@ -0,0 +1,175 @@ +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/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index 6e152497d20..a2f0a44863f 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -6,11 +6,12 @@ export default class NewCommitForm { this.branchName = form.find('.js-branch-name'); this.originalBranch = form.find('.js-original-branch'); this.createMergeRequest = form.find('.js-create-merge-request'); - this.createMergeRequestContainer = form.find('.js-create-merge-request-container'); + this.createMergeRequestContainer = form.find( + '.js-create-merge-request-container', + ); this.branchName.keyup(this.renderDestination); this.renderDestination(); } - renderDestination() { var different; different = this.branchName.val() !== this.originalBranch.val(); @@ -23,6 +24,6 @@ export default class NewCommitForm { this.createMergeRequestContainer.hide(); this.createMergeRequest.prop('checked', false); } - return this.wasDifferent = different; + return (this.wasDifferent = different); } } diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list.vue b/app/assets/javascripts/repo/components/commit_sidebar/list.vue deleted file mode 100644 index fb862e7bf01..00000000000 --- a/app/assets/javascripts/repo/components/commit_sidebar/list.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue deleted file mode 100644 index 6a0262f271b..00000000000 --- a/app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list_item.vue b/app/assets/javascripts/repo/components/commit_sidebar/list_item.vue deleted file mode 100644 index 742f746e02f..00000000000 --- a/app/assets/javascripts/repo/components/commit_sidebar/list_item.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/new_branch_form.vue b/app/assets/javascripts/repo/components/new_branch_form.vue deleted file mode 100644 index ba7090e4a9d..00000000000 --- a/app/assets/javascripts/repo/components/new_branch_form.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/new_dropdown/index.vue b/app/assets/javascripts/repo/components/new_dropdown/index.vue deleted file mode 100644 index 781404cf8ca..00000000000 --- a/app/assets/javascripts/repo/components/new_dropdown/index.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/new_dropdown/modal.vue b/app/assets/javascripts/repo/components/new_dropdown/modal.vue deleted file mode 100644 index c191af7dec3..00000000000 --- a/app/assets/javascripts/repo/components/new_dropdown/modal.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/new_dropdown/upload.vue b/app/assets/javascripts/repo/components/new_dropdown/upload.vue deleted file mode 100644 index 14ad32f4ae0..00000000000 --- a/app/assets/javascripts/repo/components/new_dropdown/upload.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue deleted file mode 100644 index a00e1e9d809..00000000000 --- a/app/assets/javascripts/repo/components/repo.vue +++ /dev/null @@ -1,63 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue deleted file mode 100644 index 4e0178072cb..00000000000 --- a/app/assets/javascripts/repo/components/repo_commit_section.vue +++ /dev/null @@ -1,178 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue deleted file mode 100644 index 37bd9003e96..00000000000 --- a/app/assets/javascripts/repo/components/repo_edit_button.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue deleted file mode 100644 index f37cbd1e961..00000000000 --- a/app/assets/javascripts/repo/components/repo_editor.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue deleted file mode 100644 index 75787ad6103..00000000000 --- a/app/assets/javascripts/repo/components/repo_file.vue +++ /dev/null @@ -1,117 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/repo/components/repo_file_buttons.vue deleted file mode 100644 index 34f0d51819a..00000000000 --- a/app/assets/javascripts/repo/components/repo_file_buttons.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue deleted file mode 100644 index 8fa637d771f..00000000000 --- a/app/assets/javascripts/repo/components/repo_loading_file.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/repo/components/repo_prev_directory.vue deleted file mode 100644 index a2b305bbd05..00000000000 --- a/app/assets/javascripts/repo/components/repo_prev_directory.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue deleted file mode 100644 index 3d1e0297bd5..00000000000 --- a/app/assets/javascripts/repo/components/repo_preview.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue deleted file mode 100644 index 4ea21913129..00000000000 --- a/app/assets/javascripts/repo/components/repo_sidebar.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/repo/components/repo_tab.vue deleted file mode 100644 index fb29a60df66..00000000000 --- a/app/assets/javascripts/repo/components/repo_tab.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/repo/components/repo_tabs.vue deleted file mode 100644 index ab0bef4f0ac..00000000000 --- a/app/assets/javascripts/repo/components/repo_tabs.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js deleted file mode 100644 index b6801af7fcb..00000000000 --- a/app/assets/javascripts/repo/index.js +++ /dev/null @@ -1,106 +0,0 @@ -import Vue from 'vue'; -import { mapActions } from 'vuex'; -import { convertPermissionToBoolean } from '../lib/utils/common_utils'; -import Repo from './components/repo.vue'; -import RepoEditButton from './components/repo_edit_button.vue'; -import newBranchForm from './components/new_branch_form.vue'; -import newDropdown from './components/new_dropdown/index.vue'; -import store from './stores'; -import Translate from '../vue_shared/translate'; - -function initRepo(el) { - if (!el) return null; - - return new Vue({ - el, - store, - components: { - repo: Repo, - }, - methods: { - ...mapActions([ - 'setInitialData', - ]), - }, - created() { - const data = el.dataset; - - this.setInitialData({ - project: { - id: data.projectId, - name: data.projectName, - url: data.projectUrl, - }, - endpoints: { - rootEndpoint: data.url, - newMergeRequestUrl: data.newMergeRequestUrl, - rootUrl: data.rootUrl, - }, - canCommit: convertPermissionToBoolean(data.canCommit), - onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch), - currentRef: data.ref, - path: data.currentPath, - currentBranch: data.currentBranch, - isRoot: convertPermissionToBoolean(data.root), - isInitialRoot: convertPermissionToBoolean(data.root), - }); - }, - render(createElement) { - return createElement('repo'); - }, - }); -} - -function initRepoEditButton(el) { - return new Vue({ - el, - store, - components: { - repoEditButton: RepoEditButton, - }, - render(createElement) { - return createElement('repo-edit-button'); - }, - }); -} - -function initNewDropdown(el) { - return new Vue({ - el, - store, - components: { - newDropdown, - }, - render(createElement) { - return createElement('new-dropdown'); - }, - }); -} - -function initNewBranchForm() { - const el = document.querySelector('.js-new-branch-dropdown'); - - if (!el) return null; - - return new Vue({ - el, - components: { - newBranchForm, - }, - store, - render(createElement) { - return createElement('new-branch-form'); - }, - }); -} - -const repo = document.getElementById('repo'); -const editButton = document.querySelector('.editable-mode'); -const newDropdownHolder = document.querySelector('.js-new-dropdown'); - -Vue.use(Translate); - -initRepo(repo); -initRepoEditButton(editButton); -initNewBranchForm(); -initNewDropdown(newDropdownHolder); diff --git a/app/assets/javascripts/repo/lib/common/disposable.js b/app/assets/javascripts/repo/lib/common/disposable.js deleted file mode 100644 index 84b29bdb600..00000000000 --- a/app/assets/javascripts/repo/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/repo/lib/common/model.js b/app/assets/javascripts/repo/lib/common/model.js deleted file mode 100644 index 23c4811e6c0..00000000000 --- a/app/assets/javascripts/repo/lib/common/model.js +++ /dev/null @@ -1,56 +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 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/repo/lib/common/model_manager.js b/app/assets/javascripts/repo/lib/common/model_manager.js deleted file mode 100644 index fd462252795..00000000000 --- a/app/assets/javascripts/repo/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/repo/lib/decorations/controller.js b/app/assets/javascripts/repo/lib/decorations/controller.js deleted file mode 100644 index 0954b7973c4..00000000000 --- a/app/assets/javascripts/repo/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/repo/lib/diff/controller.js b/app/assets/javascripts/repo/lib/diff/controller.js deleted file mode 100644 index dc0b1c95e59..00000000000 --- a/app/assets/javascripts/repo/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/repo/lib/diff/diff.js b/app/assets/javascripts/repo/lib/diff/diff.js deleted file mode 100644 index 0e37f5c4704..00000000000 --- a/app/assets/javascripts/repo/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/repo/lib/diff/diff_worker.js b/app/assets/javascripts/repo/lib/diff/diff_worker.js deleted file mode 100644 index e74c4046330..00000000000 --- a/app/assets/javascripts/repo/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/repo/lib/editor.js b/app/assets/javascripts/repo/lib/editor.js deleted file mode 100644 index db499444402..00000000000 --- a/app/assets/javascripts/repo/lib/editor.js +++ /dev/null @@ -1,79 +0,0 @@ -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), - ); - } - - createInstance(domElement) { - if (!this.instance) { - this.disposable.add( - this.instance = this.monaco.editor.create(domElement, { - model: null, - readOnly: false, - contextmenu: true, - scrollBeyondLastLine: false, - }), - this.dirtyDiffController = new DirtyDiffController( - this.modelManager, this.decorationsController, - ), - ); - } - } - - createModel(file) { - return this.modelManager.addModel(file); - } - - attachModel(model) { - this.instance.setModel(model.getModel()); - 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; - }, {})); - - this.dirtyDiffController.reDecorate(model); - } - - clearEditor() { - if (this.instance) { - this.instance.setModel(null); - } - } - - dispose() { - this.disposable.dispose(); - - // dispose main monaco instance - if (this.instance) { - this.instance = null; - } - } -} diff --git a/app/assets/javascripts/repo/lib/editor_options.js b/app/assets/javascripts/repo/lib/editor_options.js deleted file mode 100644 index 701affc466e..00000000000 --- a/app/assets/javascripts/repo/lib/editor_options.js +++ /dev/null @@ -1,2 +0,0 @@ -export default [{ -}]; diff --git a/app/assets/javascripts/repo/monaco_loader.js b/app/assets/javascripts/repo/monaco_loader.js deleted file mode 100644 index af83a1ec0b4..00000000000 --- a/app/assets/javascripts/repo/monaco_loader.js +++ /dev/null @@ -1,11 +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 - }, -}); - -// eslint-disable-next-line no-underscore-dangle -window.__monaco_context__ = monacoContext; -export default monacoContext.require; diff --git a/app/assets/javascripts/repo/services/index.js b/app/assets/javascripts/repo/services/index.js deleted file mode 100644 index 994d325e991..00000000000 --- a/app/assets/javascripts/repo/services/index.js +++ /dev/null @@ -1,44 +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()); - }, - getBranchData(projectId, currentBranch) { - return Api.branchSingle(projectId, currentBranch); - }, - 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/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js deleted file mode 100644 index af5dcf054ef..00000000000 --- a/app/assets/javascripts/repo/stores/actions.js +++ /dev/null @@ -1,146 +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'; - -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 checkCommitStatus = ({ state }) => service.getBranchData( - state.project.id, - state.currentBranch, -) - .then((data) => { - const { id } = data.commit; - - if (state.currentRef !== id) { - return true; - } - - return false; - }) - .catch(() => flash('Error checking branch data. Please try again.')); - -export const commitChanges = ({ commit, state, dispatch, getters }, { payload, newMr }) => - service.commit(state.project.id, payload) - .then((data) => { - const { branch } = payload; - if (!data.short_id) { - flash(data.message); - return; - } - - const lastCommit = { - commit_path: `${state.project.url}/commit/${data.id}`, - commit: { - message: data.message, - authored_date: data.committed_date, - }, - }; - - flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice'); - - if (newMr) { - dispatch('redirectToUrl', `${state.endpoints.newMergeRequestUrl}${branch}`); - } else { - commit(types.SET_COMMIT_REF, data.id); - - getters.changedFiles.forEach((entry) => { - commit(types.SET_LAST_COMMIT_DATA, { - entry, - lastCommit, - }); - }); - - dispatch('discardAllChanges'); - dispatch('closeAllFiles'); - dispatch('toggleEditMode'); - - window.scrollTo(0, 0); - } - }) - .catch(() => flash('Error committing changes. Please try again.')); - -export const createTempEntry = ({ state, dispatch }, { name, type, content = '', base64 = false }) => { - if (type === 'tree') { - dispatch('createTempTree', name); - } else if (type === 'blob') { - dispatch('createTempFile', { - tree: state, - name, - base64, - content, - }); - } -}; - -export const popHistoryState = ({ state, dispatch, getters }) => { - const treeList = getters.treeList; - const tree = treeList.find(file => file.url === state.previousUrl); - - if (!tree) return; - - if (tree.type === 'tree') { - dispatch('toggleTreeOpen', { endpoint: tree.url, tree }); - } -}; - -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/branch'; diff --git a/app/assets/javascripts/repo/stores/actions/branch.js b/app/assets/javascripts/repo/stores/actions/branch.js deleted file mode 100644 index 61d9a5af3e3..00000000000 --- a/app/assets/javascripts/repo/stores/actions/branch.js +++ /dev/null @@ -1,20 +0,0 @@ -import service from '../../services'; -import * as types from '../mutation_types'; -import { pushState } from '../utils'; - -// eslint-disable-next-line import/prefer-default-export -export const createNewBranch = ({ state, commit }, branch) => service.createBranch( - state.project.id, - { - branch, - ref: state.currentBranch, - }, -).then(res => res.json()) -.then((data) => { - const branchName = data.name; - const url = location.href.replace(state.currentBranch, branchName); - - pushState(url); - - commit(types.SET_CURRENT_BRANCH, branchName); -}); diff --git a/app/assets/javascripts/repo/stores/actions/file.js b/app/assets/javascripts/repo/stores/actions/file.js deleted file mode 100644 index 5bae4fa826a..00000000000 --- a/app/assets/javascripts/repo/stores/actions/file.js +++ /dev/null @@ -1,110 +0,0 @@ -import { normalizeHeaders } from '../../../lib/utils/common_utils'; -import flash from '../../../flash'; -import service from '../../services'; -import * as types from '../mutation_types'; -import { - findEntry, - pushState, - 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) { - pushState(file.parentTreeUrl); - } - - 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 = ''; -}; - -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); - - pushState(file.url); - }) - .catch(() => { - commit(types.TOGGLE_LOADING, file); - flash('Error loading file data. Please try again.'); - }); -}; - -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.')); - -export const changeFileContent = ({ commit }, { file, content }) => { - commit(types.UPDATE_FILE_CONTENT, { file, content }); -}; - -export const createTempFile = ({ state, commit, dispatch }, { tree, name, content = '', base64 = '' }) => { - const file = createTemp({ - name: name.replace(`${state.path}/`, ''), - path: tree.path, - type: 'blob', - level: tree.level !== undefined ? tree.level + 1 : 0, - changed: true, - content, - base64, - }); - - if (findEntry(tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`); - - commit(types.CREATE_TMP_FILE, { - parent: tree, - file, - }); - commit(types.TOGGLE_FILE_OPEN, file); - dispatch('setFileActive', file); - - if (!state.editMode && !file.base64) { - dispatch('toggleEditMode', true); - } - - return Promise.resolve(file); -}; diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js deleted file mode 100644 index 7c251e26bed..00000000000 --- a/app/assets/javascripts/repo/stores/actions/tree.js +++ /dev/null @@ -1,163 +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 { - pushState, - setPageTitle, - findEntry, - createTemp, - createOrMergeEntry, -} from '../utils'; - -export const getTreeData = ( - { commit, state, dispatch }, - { endpoint = state.endpoints.rootEndpoint, tree = state } = {}, -) => { - commit(types.TOGGLE_LOADING, tree); - - service.getTreeData(endpoint) - .then((res) => { - const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); - - setPageTitle(pageTitle); - - return res.json(); - }) - .then((data) => { - const prevLastCommitPath = tree.lastCommitPath; - if (!state.isInitialRoot) { - commit(types.SET_ROOT, data.path === '/'); - } - - dispatch('updateDirectoryData', { data, tree }); - commit(types.SET_PARENT_TREE_URL, data.parent_tree_url); - commit(types.SET_LAST_COMMIT_URL, { tree, url: data.last_commit_path }); - commit(types.TOGGLE_LOADING, tree); - - if (prevLastCommitPath !== null) { - dispatch('getLastCommitData', tree); - } - - pushState(endpoint); - }) - .catch(() => { - flash('Error loading tree data. Please try again.'); - commit(types.TOGGLE_LOADING, tree); - }); -}; - -export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => { - if (tree.opened) { - // send empty data to clear the tree - const data = { trees: [], blobs: [], submodules: [] }; - - pushState(tree.parentTreeUrl); - - commit(types.SET_PREVIOUS_URL, tree.parentTreeUrl); - dispatch('updateDirectoryData', { data, tree }); - } else { - commit(types.SET_PREVIOUS_URL, endpoint); - dispatch('getTreeData', { endpoint, tree }); - } - - commit(types.TOGGLE_TREE_OPEN, tree); -}; - -export const clickedTreeRow = ({ 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 }, name) => { - let tree = state; - const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/'); - - dirNames.forEach((dirName) => { - const foundEntry = findEntry(tree, 'tree', dirName); - - if (!foundEntry) { - const tmpEntry = createTemp({ - name: dirName, - path: tree.path, - type: 'tree', - level: tree.level !== undefined ? tree.level + 1 : 0, - }); - - commit(types.CREATE_TMP_TREE, { - parent: tree, - tmpEntry, - }); - commit(types.TOGGLE_TREE_OPEN, tmpEntry); - - tree = tmpEntry; - } else { - tree = foundEntry; - } - }); - - if (tree.tempFile) { - dispatch('createTempFile', { - tree, - name: '.gitkeep', - }); - } -}; - -export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => { - if (tree.lastCommitPath === null || getters.isCollapsed) 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, lastCommit.type, lastCommit.file_name); - - if (entry) { - commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit }); - } - }); - - dispatch('getLastCommitData', tree); - }) - .catch(() => flash('Error fetching log data.')); -}; - -export const updateDirectoryData = ({ commit, state }, { data, tree }) => { - const level = tree.level !== undefined ? tree.level + 1 : 0; - const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl; - const createEntry = (entry, type) => createOrMergeEntry({ - tree, - 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, data: formattedData }); -}; diff --git a/app/assets/javascripts/repo/stores/getters.js b/app/assets/javascripts/repo/stores/getters.js deleted file mode 100644 index 5ce9f449905..00000000000 --- a/app/assets/javascripts/repo/stores/getters.js +++ /dev/null @@ -1,40 +0,0 @@ -import _ from 'underscore'; - -/* - 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) => { - const mapTree = arr => (!arr.tree.length ? [] : _.map(arr.tree, a => [a, mapTree(a)])); - - return _.chain(state.tree) - .map(arr => [arr, mapTree(arr)]) - .flatten() - .value(); -}; - -export const changedFiles = state => state.openFiles.filter(file => file.changed); - -export const activeFile = state => state.openFiles.find(file => file.active); - -export const activeFileExtension = (state) => { - const file = activeFile(state); - return file ? `.${file.path.split('.').pop()}` : ''; -}; - -export const isCollapsed = state => !!state.openFiles.length; - -export const canEditFile = (state) => { - const currentActiveFile = activeFile(state); - const openedFiles = state.openFiles; - - return state.canCommit && - state.onTopOfBranch && - openedFiles.length && - (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/repo/stores/index.js b/app/assets/javascripts/repo/stores/index.js deleted file mode 100644 index 6ac9bfd8189..00000000000 --- a/app/assets/javascripts/repo/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/repo/stores/mutation_types.js b/app/assets/javascripts/repo/stores/mutation_types.js deleted file mode 100644 index bc3390f1506..00000000000 --- a/app/assets/javascripts/repo/stores/mutation_types.js +++ /dev/null @@ -1,30 +0,0 @@ -export const SET_INITIAL_DATA = 'SET_INITIAL_DATA'; -export const TOGGLE_LOADING = 'TOGGLE_LOADING'; -export const SET_COMMIT_REF = 'SET_COMMIT_REF'; -export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL'; -export const SET_ROOT = 'SET_ROOT'; -export const SET_PREVIOUS_URL = 'SET_PREVIOUS_URL'; -export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA'; - -// 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'; - -// 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 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/repo/stores/mutations.js b/app/assets/javascripts/repo/stores/mutations.js deleted file mode 100644 index ae2ba5bedf7..00000000000 --- a/app/assets/javascripts/repo/stores/mutations.js +++ /dev/null @@ -1,61 +0,0 @@ -import * as types from './mutation_types'; -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_COMMIT_REF](state, ref) { - Object.assign(state, { - currentRef: ref, - }); - }, - [types.SET_ROOT](state, isRoot) { - Object.assign(state, { - isRoot, - isInitialRoot: isRoot, - }); - }, - [types.SET_PREVIOUS_URL](state, previousUrl) { - Object.assign(state, { - previousUrl, - }); - }, - [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) { - Object.assign(entry.lastCommit, { - url: lastCommit.commit_path, - message: lastCommit.commit.message, - updatedAt: lastCommit.commit.authored_date, - }); - }, - ...fileMutations, - ...treeMutations, - ...branchMutations, -}; diff --git a/app/assets/javascripts/repo/stores/mutations/branch.js b/app/assets/javascripts/repo/stores/mutations/branch.js deleted file mode 100644 index d8229e8a620..00000000000 --- a/app/assets/javascripts/repo/stores/mutations/branch.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as types from '../mutation_types'; - -export default { - [types.SET_CURRENT_BRANCH](state, currentBranch) { - Object.assign(state, { - currentBranch, - }); - }, -}; diff --git a/app/assets/javascripts/repo/stores/mutations/file.js b/app/assets/javascripts/repo/stores/mutations/file.js deleted file mode 100644 index f9ba80b9dc2..00000000000 --- a/app/assets/javascripts/repo/stores/mutations/file.js +++ /dev/null @@ -1,54 +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, - }); - }, - [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.DISCARD_FILE_CHANGES](state, file) { - Object.assign(file, { - content: '', - changed: false, - }); - }, - [types.CREATE_TMP_FILE](state, { file, parent }) { - parent.tree.push(file); - }, -}; diff --git a/app/assets/javascripts/repo/stores/mutations/tree.js b/app/assets/javascripts/repo/stores/mutations/tree.js deleted file mode 100644 index 130221c9fda..00000000000 --- a/app/assets/javascripts/repo/stores/mutations/tree.js +++ /dev/null @@ -1,27 +0,0 @@ -import * as types from '../mutation_types'; - -export default { - [types.TOGGLE_TREE_OPEN](state, tree) { - Object.assign(tree, { - opened: !tree.opened, - }); - }, - [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/repo/stores/state.js b/app/assets/javascripts/repo/stores/state.js deleted file mode 100644 index 0068834831e..00000000000 --- a/app/assets/javascripts/repo/stores/state.js +++ /dev/null @@ -1,24 +0,0 @@ -export default () => ({ - canCommit: false, - currentBranch: '', - currentBlobView: 'repo-preview', - currentRef: '', - discardPopupOpen: false, - editMode: false, - endpoints: {}, - isRoot: false, - isInitialRoot: false, - lastCommitPath: '', - loading: false, - onTopOfBranch: false, - openFiles: [], - path: '', - project: { - id: 0, - name: '', - url: '', - }, - parentTreeUrl: '', - previousUrl: '', - tree: [], -}); diff --git a/app/assets/javascripts/repo/stores/utils.js b/app/assets/javascripts/repo/stores/utils.js deleted file mode 100644 index fae1f4439a9..00000000000 --- a/app/assets/javascripts/repo/stores/utils.js +++ /dev/null @@ -1,127 +0,0 @@ -export const dataStructure = () => ({ - id: '', - key: '', - type: '', - name: '', - url: '', - path: '', - level: 0, - tempFile: false, - icon: '', - tree: [], - loading: false, - opened: false, - active: false, - changed: false, - lastCommitPath: '', - lastCommit: { - url: '', - message: '', - updatedAt: '', - }, - tree_url: '', - blamePath: '', - commitsPath: '', - permalink: '', - rawPath: '', - binary: false, - html: '', - raw: '', - content: '', - parentTreeUrl: '', - renderError: false, - base64: false, -}); - -export const decorateData = (entity) => { - const { - id, - 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, - key: `${name}-${type}-${id}`, - type, - name, - url, - tree_url, - path, - level, - tempFile, - icon: `fa-${icon}`, - opened, - active, - parentTreeUrl, - changed, - renderError, - content, - base64, - }; -}; - -export const findEntry = (state, type, name) => state.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 pushState = (url) => { - history.pushState({ url }, '', url); -}; - -export const createTemp = ({ name, path, type, level, changed, content, base64 }) => { - const treePath = path ? `${path}/${name}` : name; - - return decorateData({ - id: new Date().getTime().toString(), - name, - type, - tempFile: true, - path: treePath, - icon: type === 'tree' ? 'folder' : 'file-text-o', - changed, - content, - parentTreeUrl: '', - level, - base64, - renderError: base64, - }); -}; - -export const createOrMergeEntry = ({ tree, entry, type, parentTreeUrl, level }) => { - const found = findEntry(tree, type, entry.name); - - if (found) { - return Object.assign({}, found, { - id: entry.id, - url: entry.url, - tempFile: false, - }); - } - - return decorateData({ - ...entry, - type, - parentTreeUrl, - level, - }); -}; diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue new file mode 100644 index 00000000000..dce23bd65f6 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue @@ -0,0 +1,103 @@ + + + -- cgit v1.2.1