Commit e243dade authored by Micaël Bergeron's avatar Micaël Bergeron

Merge remote-tracking branch 'origin/master' into ee-40781-os-to-ce

parents c34a1332 9e9748e6
...@@ -22,8 +22,8 @@ gem 'faraday', '~> 0.12' ...@@ -22,8 +22,8 @@ gem 'faraday', '~> 0.12'
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.2' gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0' gem 'doorkeeper', '~> 4.3'
gem 'doorkeeper-openid_connect', '~> 1.2.0' gem 'doorkeeper-openid_connect', '~> 1.3'
gem 'omniauth', '~> 1.4.2' gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.9' gem 'omniauth-azure-oauth2', '~> 0.0.9'
...@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2' ...@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.2' gem 'omniauth-google-oauth2', '~> 0.5.2'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2' gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.10.0' gem 'omniauth-saml', '~> 1.10'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
...@@ -116,7 +116,7 @@ gem 'fog-rackspace', '~> 0.1.1' ...@@ -116,7 +116,7 @@ gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.2.0' gem 'fog-aliyun', '~> 0.2.0'
# for Google storage # for Google storage
gem 'google-api-client', '~> 0.19' gem 'google-api-client', '~> 0.19.8'
# for aws storage # for aws storage
gem 'unf', '~> 0.1.4' gem 'unf', '~> 0.1.4'
...@@ -245,9 +245,6 @@ gem 'mousetrap-rails', '~> 1.4.6' ...@@ -245,9 +245,6 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.5' gem 'charlock_holmes', '~> 0.7.5'
# Faster JSON
gem 'oj', '~> 2.17.4'
# Faster blank # Faster blank
gem 'fast_blank' gem 'fast_blank'
...@@ -291,7 +288,6 @@ gem 'batch-loader', '~> 1.2.1' ...@@ -291,7 +288,6 @@ gem 'batch-loader', '~> 1.2.1'
# Perf bar # Perf bar
gem 'peek', '~> 1.0.1' gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2' gem 'peek-gc', '~> 0.0.2'
gem 'peek-host', '~> 1.0.0'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-performance_bar', '~> 1.3.0' gem 'peek-performance_bar', '~> 1.3.0'
gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-pg', '~> 1.3.0', group: :postgres
......
...@@ -47,6 +47,7 @@ GEM ...@@ -47,6 +47,7 @@ GEM
memoizable (~> 0.4.0) memoizable (~> 0.4.0)
addressable (2.5.2) addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0) public_suffix (>= 2.0.2, < 4.0)
aes_key_wrap (1.0.1)
akismet (2.0.0) akismet (2.0.0)
allocations (1.0.5) allocations (1.0.5)
arel (6.0.4) arel (6.0.4)
...@@ -94,7 +95,7 @@ GEM ...@@ -94,7 +95,7 @@ GEM
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubis (>= 2.6.6) erubis (>= 2.6.6)
rack (>= 0.9.0) rack (>= 0.9.0)
bindata (2.4.1) bindata (2.4.3)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blankslate (2.1.2.4) blankslate (2.1.2.4)
...@@ -184,10 +185,10 @@ GEM ...@@ -184,10 +185,10 @@ GEM
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20170404) domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.6) doorkeeper (4.3.1)
railties (>= 4.2) railties (>= 4.2)
doorkeeper-openid_connect (1.2.0) doorkeeper-openid_connect (1.3.0)
doorkeeper (~> 4.0) doorkeeper (~> 4.3)
json-jwt (~> 1.6) json-jwt (~> 1.6)
dropzonejs-rails (0.7.2) dropzonejs-rails (0.7.2)
rails (> 3.1) rails (> 3.1)
...@@ -457,10 +458,10 @@ GEM ...@@ -457,10 +458,10 @@ GEM
jmespath (1.3.1) jmespath (1.3.1)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
json (1.8.6) json (1.8.6)
json-jwt (1.7.2) json-jwt (1.9.2)
activesupport activesupport
aes_key_wrap
bindata bindata
multi_json (>= 1.3)
securecompare securecompare
url_safe_base64 url_safe_base64
json-schema (2.8.0) json-schema (2.8.0)
...@@ -553,7 +554,6 @@ GEM ...@@ -553,7 +554,6 @@ GEM
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.8.0) octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.5)
omniauth (1.4.3) omniauth (1.4.3)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
...@@ -623,8 +623,6 @@ GEM ...@@ -623,8 +623,6 @@ GEM
railties (>= 4.0.0) railties (>= 4.0.0)
peek-gc (0.0.2) peek-gc (0.0.2)
peek peek
peek-host (1.0.0)
peek
peek-mysql2 (1.1.0) peek-mysql2 (1.1.0)
atomic (>= 1.0.0) atomic (>= 1.0.0)
mysql2 mysql2
...@@ -688,7 +686,7 @@ GEM ...@@ -688,7 +686,7 @@ GEM
httpclient (>= 2.4) httpclient (>= 2.4)
multi_json (>= 1.3.6) multi_json (>= 1.3.6)
rack (>= 1.1) rack (>= 1.1)
rack-protection (1.5.3) rack-protection (2.0.1)
rack rack
rack-proxy (0.6.0) rack-proxy (0.6.0)
rack rack
...@@ -707,8 +705,8 @@ GEM ...@@ -707,8 +705,8 @@ GEM
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.8) rails-dom-testing (1.0.9)
activesupport (>= 4.2.0.beta, < 5.0) activesupport (>= 4.2.0, < 5.0)
nokogiri (~> 1.6) nokogiri (~> 1.6)
rails-deprecated_sanitizer (>= 1.0.1) rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3) rails-html-sanitizer (1.0.3)
...@@ -1061,8 +1059,8 @@ DEPENDENCIES ...@@ -1061,8 +1059,8 @@ DEPENDENCIES
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
doorkeeper (~> 4.2.0) doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.2.0) doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
elasticsearch-api (= 5.0.3) elasticsearch-api (= 5.0.3)
elasticsearch-model (~> 0.1.9) elasticsearch-model (~> 0.1.9)
...@@ -1103,7 +1101,7 @@ DEPENDENCIES ...@@ -1103,7 +1101,7 @@ DEPENDENCIES
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.19) google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1) google-protobuf (= 3.5.1)
gpgme gpgme
grape (~> 1.0) grape (~> 1.0)
...@@ -1144,7 +1142,6 @@ DEPENDENCIES ...@@ -1144,7 +1142,6 @@ DEPENDENCIES
nokogiri (~> 1.8.2) nokogiri (~> 1.8.2)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.8) octokit (~> 4.8)
oj (~> 2.17.4)
omniauth (~> 1.4.2) omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.1) omniauth-authentiq (~> 0.3.1)
...@@ -1156,14 +1153,13 @@ DEPENDENCIES ...@@ -1156,14 +1153,13 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.5.2) omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10.0) omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
peek-mysql2 (~> 1.1.0) peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0) peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0) peek-pg (~> 1.3.0)
......
import './autosize'; import './autosize';
import './bind_in_out'; import './bind_in_out';
import initCopyAsGFM from './copy_as_gfm'; import './markdown/render_gfm';
import initCopyAsGFM from './markdown/copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard'; import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior'; import './details_behavior';
import installGlEmojiElement from './gl_emoji'; import installGlEmojiElement from './gl_emoji';
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import { insertText, getSelectedFragment, nodeMatchesSelector } from '../lib/utils/common_utils'; import { insertText, getSelectedFragment, nodeMatchesSelector } from '~/lib/utils/common_utils';
import { placeholderImage } from '../lazy_loader'; import { placeholderImage } from '~/lazy_loader';
const gfmRules = { const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
......
import $ from 'jquery'; import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math'; import renderMath from './render_math';
import renderMermaid from './render_mermaid'; import renderMermaid from './render_mermaid';
import syntaxHighlight from './syntax_highlight';
// Render Gitlab flavoured Markdown // Render Gitlab flavoured Markdown
// //
......
import $ from 'jquery'; import $ from 'jquery';
import { __ } from './locale'; import { __ } from '~/locale';
import flash from './flash'; import flash from '~/flash';
// Renders math using KaTeX in any element with the // Renders math using KaTeX in any element with the
// `js-render-math` class // `js-render-math` class
......
import flash from '~/flash';
// Renders diagrams and flowcharts from text using Mermaid in any element with the // Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class. // `js-render-mermaid` class.
// //
...@@ -12,8 +14,6 @@ ...@@ -12,8 +14,6 @@
// </pre> // </pre>
// //
import Flash from './flash';
export default function renderMermaid($els) { export default function renderMermaid($els) {
if (!$els.length) return; if (!$els.length) return;
...@@ -52,6 +52,6 @@ export default function renderMermaid($els) { ...@@ -52,6 +52,6 @@ export default function renderMermaid($els) {
}); });
}); });
}).catch((err) => { }).catch((err) => {
Flash(`Can't load mermaid module: ${err}`); flash(`Can't load mermaid module: ${err}`);
}); });
} }
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */
import $ from 'jquery'; import $ from 'jquery';
import { rstrip } from './lib/utils/common_utils'; import { rstrip } from './lib/utils/common_utils';
window.ConfirmDangerModal = (function() { function openConfirmDangerModal($form, text) {
function ConfirmDangerModal(form, text, arg) { $('.js-confirm-text').text(text || '');
var project_path, submit, warningMessage; $('.js-confirm-danger-input').val('');
warningMessage = (arg != null ? arg : {}).warningMessage; $('#modal-confirm-danger').modal('show');
this.form = form;
$('.js-confirm-text').html(text || ''); const confirmTextMatch = $('.js-confirm-danger-match').text();
if (warningMessage) { const $submit = $('.js-confirm-danger-submit');
$('.js-warning-text').html(warningMessage); $submit.disable();
$('.js-confirm-danger-input').off('input').on('input', function handleInput() {
const confirmText = rstrip($(this).val());
if (confirmText === confirmTextMatch) {
$submit.enable();
} else {
$submit.disable();
} }
$('.js-confirm-danger-input').val(''); });
$('#modal-confirm-danger').modal('show'); $('.js-confirm-danger-submit').off('click').on('click', () => $form.submit());
project_path = $('.js-confirm-danger-match').text(); }
submit = $('.js-confirm-danger-submit');
submit.disable();
$('.js-confirm-danger-input').off('input');
$('.js-confirm-danger-input').on('input', function() {
if (rstrip($(this).val()) === project_path) {
return submit.enable();
} else {
return submit.disable();
}
});
$('.js-confirm-danger-submit').off('click');
$('.js-confirm-danger-submit').on('click', (function(_this) {
return function() {
return _this.form.submit();
};
})(this));
}
return ConfirmDangerModal; export default function initConfirmDangerModal() {
})(); $(document).on('click', '.js-confirm-danger', (e) => {
e.preventDefault();
const $btn = $(e.target);
const $form = $btn.closest('form');
const text = $btn.data('confirmDangerMessage');
openConfirmDangerModal($form, text);
});
}
...@@ -53,8 +53,12 @@ function initPageShortcuts(page) { ...@@ -53,8 +53,12 @@ function initPageShortcuts(page) {
function initGFMInput() { function initGFMInput() {
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); const gfm = new GfmAutoComplete(
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
);
const enableGFM = convertPermissionToBoolean(
el.dataset.supportsAutocomplete,
);
gfm.setup($(el), { gfm.setup($(el), {
emojis: true, emojis: true,
members: enableGFM, members: enableGFM,
...@@ -67,9 +71,9 @@ function initGFMInput() { ...@@ -67,9 +71,9 @@ function initGFMInput() {
} }
function initPerformanceBar() { function initPerformanceBar() {
if (document.querySelector('#peek')) { if (document.querySelector('#js-peek')) {
import('./performance_bar') import('./performance_bar')
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap .then(m => new m.default({ container: '#js-peek' })) // eslint-disable-line new-cap
.catch(() => Flash('Error loading performance bar module')); .catch(() => Flash('Error loading performance bar module'));
} }
} }
......
...@@ -2,7 +2,7 @@ import $ from 'jquery'; ...@@ -2,7 +2,7 @@ import $ from 'jquery';
import autosize from 'autosize'; import autosize from 'autosize';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
import dropzoneInput from './dropzone_input'; import dropzoneInput from './dropzone_input';
import textUtils from './lib/utils/text_markdown'; import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
export default class GLForm { export default class GLForm {
constructor(form, enableGFM = false) { constructor(form, enableGFM = false) {
...@@ -47,7 +47,7 @@ export default class GLForm { ...@@ -47,7 +47,7 @@ export default class GLForm {
} }
// form and textarea event listeners // form and textarea event listeners
this.addEventListeners(); this.addEventListeners();
textUtils.init(this.form); addMarkdownListeners(this.form);
// hide discard button // hide discard button
this.form.find('.js-note-discard').hide(); this.form.find('.js-note-discard').hide();
this.form.show(); this.form.show();
...@@ -86,7 +86,7 @@ export default class GLForm { ...@@ -86,7 +86,7 @@ export default class GLForm {
clearEventListeners() { clearEventListeners() {
this.textarea.off('focus'); this.textarea.off('focus');
this.textarea.off('blur'); this.textarea.off('blur');
textUtils.removeListeners(this.form); removeMarkdownListeners(this.form);
} }
addEventListeners() { addEventListeners() {
......
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import * as consts from 'ee/ide/stores/modules/commit/constants'; import * as consts from '../../stores/modules/commit/constants';
import RadioGroup from './radio_group.vue'; import RadioGroup from './radio_group.vue';
export default { export default {
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import router from 'ee/ide/ide_router';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import router from '../../ide_router';
export default { export default {
components: { components: {
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<button <button
type="button" type="button"
class="btn btn-blank multi-file-discard-btn" class="btn btn-blank multi-file-discard-btn"
@click="discardFileChanges(file)" @click="discardFileChanges(file.path)"
> >
Discard Discard
</button> </button>
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import repoTabs from './repo_tabs.vue'; import repoTabs from './repo_tabs.vue';
import repoFileButtons from './repo_file_buttons.vue'; import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue'; import ideStatusBar from './ide_status_bar.vue';
import repoPreview from './repo_preview.vue';
import repoEditor from './repo_editor.vue'; import repoEditor from './repo_editor.vue';
export default { export default {
...@@ -16,7 +15,6 @@ ...@@ -16,7 +15,6 @@
repoFileButtons, repoFileButtons,
ideStatusBar, ideStatusBar,
repoEditor, repoEditor,
repoPreview,
}, },
props: { props: {
emptyStateSvgPath: { emptyStateSvgPath: {
...@@ -33,18 +31,12 @@ ...@@ -33,18 +31,12 @@
}, },
}, },
computed: { computed: {
...mapState([ ...mapState(['changedFiles', 'openFiles', 'viewer']),
'currentBlobView', ...mapGetters(['activeFile', 'hasChanges']),
'selectedFile',
'changedFiles',
]),
...mapGetters([
'activeFile',
]),
}, },
mounted() { mounted() {
const returnValue = 'Are you sure you want to lose unsaved changes?'; const returnValue = 'Are you sure you want to lose unsaved changes?';
window.onbeforeunload = (e) => { window.onbeforeunload = e => {
if (!this.changedFiles.length) return undefined; if (!this.changedFiles.length) return undefined;
Object.assign(e, { Object.assign(e, {
...@@ -67,20 +59,29 @@ ...@@ -67,20 +59,29 @@
<template <template
v-if="activeFile" v-if="activeFile"
> >
<repo-tabs/> <repo-tabs
<component :files="openFiles"
:viewer="viewer"
:has-changes="hasChanges"
/>
<repo-editor
class="multi-file-edit-pane-content" class="multi-file-edit-pane-content"
:is="currentBlobView" :file="activeFile"
/>
<repo-file-buttons
:file="activeFile"
/> />
<repo-file-buttons />
<ide-status-bar <ide-status-bar
:file="selectedFile" :file="activeFile"
/> />
</template> </template>
<template <template
v-else v-else
> >
<div class="ide-empty-state"> <div
v-once
class="ide-empty-state"
>
<div class="row js-empty-state"> <div class="row js-empty-state">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="svg-content svg-250"> <div class="svg-content svg-250">
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue'; import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import repoCommitSection from './repo_commit_section.vue'; import repoCommitSection from './repo_commit_section.vue';
import ResizablePanel from './resizable_panel.vue';
export default { export default {
components: { components: {
repoCommitSection, repoCommitSection,
icon, icon,
panelResizer, panelResizer,
ResizablePanel,
},
props: {
noChangesStateSvgPath: {
type: String,
required: true,
}, },
props: { committedStateSvgPath: {
noChangesStateSvgPath: { type: String,
type: String, required: true,
required: true,
},
committedStateSvgPath: {
type: String,
required: true,
},
}, },
data() { },
return { computed: {
width: 340, ...mapState(['changedFiles', 'rightPanelCollapsed']),
}; ...mapGetters(['currentIcon']),
}, },
computed: { methods: {
...mapState([ ...mapActions(['setPanelCollapsedStatus']),
'rightPanelCollapsed', },
'changedFiles', };
]),
currentIcon() {
return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
},
maxSize() {
return window.innerWidth / 2;
},
panelStyle() {
if (!this.rightPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
},
},
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
]),
toggleCollapsed() {
this.setPanelCollapsedStatus({
side: 'right',
collapsed: !this.rightPanelCollapsed,
});
},
toggleFullbarCollapsed() {
if (this.rightPanelCollapsed) {
this.toggleCollapsed();
}
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
},
};
</script> </script>
<template> <template>
<div <resizable-panel
class="multi-file-commit-panel" :collapsible="true"
:class="{ :initial-width="340"
'is-collapsed': rightPanelCollapsed, side="right"
}"
:style="panelStyle"
@click="toggleFullbarCollapsed"
> >
<div <div
class="multi-file-commit-panel-section" class="multi-file-commit-panel-section"
...@@ -104,7 +64,10 @@ ...@@ -104,7 +64,10 @@
<button <button
type="button" type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn" class="btn btn-transparent multi-file-commit-panel-collapse-btn"
@click.stop="toggleCollapsed" @click.stop="setPanelCollapsedStatus({
side: 'right',
collapsed: !rightPanelCollapsed,
})"
> >
<icon <icon
:name="currentIcon" :name="currentIcon"
...@@ -117,15 +80,5 @@ ...@@ -117,15 +80,5 @@
:committed-state-svg-path="committedStateSvgPath" :committed-state-svg-path="committedStateSvgPath"
/> />
</div> </div>
<panel-resizer </resizable-panel>
:size.sync="width"
:enabled="!rightPanelCollapsed"
:start-size="340"
:min-size="200"
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="left"
/>
</div>
</template> </template>
<script> <script>
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import repoTree from './ide_repo_tree.vue'; import repoTree from './ide_repo_tree.vue';
import newDropdown from './new_dropdown/index.vue'; import newDropdown from './new_dropdown/index.vue';
export default { export default {
components: { components: {
repoTree, repoTree,
icon, icon,
newDropdown, newDropdown,
},
props: {
projectId: {
type: String,
required: true,
}, },
branch: { props: {
type: Object, projectId: {
required: true, type: String,
required: true,
},
branch: {
type: Object,
required: true,
},
}, },
}, };
};
</script> </script>
<template> <template>
...@@ -40,8 +40,8 @@ export default { ...@@ -40,8 +40,8 @@ export default {
/> />
</div> </div>
</div> </div>
<div> <repo-tree
<repo-tree :tree-id="branch.treeId" /> :tree="branch.tree"
</div> />
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import RepoFile from './repo_file.vue';
import repoFile from './repo_file.vue';
export default { export default {
components: { components: {
repoFile, RepoFile,
skeletonLoadingContainer, SkeletonLoadingContainer,
}, },
props: { props: {
treeId: { tree: {
type: String, type: Object,
required: true, required: true,
}, },
}, },
computed: {
...mapState([
'trees',
]),
...mapState({
projectName(state) {
return state.project.name;
},
}),
selctedTree() {
return this.trees[this.treeId].tree;
},
showLoading() {
return !this.trees[this.treeId] || this.trees[this.treeId].loading;
},
},
}; };
</script> </script>
<template> <template>
<div <div
class="ide-file-list" class="ide-file-list"
v-if="treeId"
> >
<template v-if="showLoading"> <template v-if="tree.loading">
<div <div
class="multi-file-loading-container" class="multi-file-loading-container"
v-for="n in 3" v-for="n in 3"
...@@ -47,10 +29,13 @@ export default { ...@@ -47,10 +29,13 @@ export default {
<skeleton-loading-container /> <skeleton-loading-container />
</div> </div>
</template> </template>
<repo-file <template v-else>
v-for="file in selctedTree" <repo-file
:key="file.key" v-for="file in tree.tree"
:file="file" :key="file.key"
/> :file="file"
:level="0"
/>
</template>
</div> </div>
</template> </template>
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue'; import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import projectTree from './ide_project_tree.vue'; import projectTree from './ide_project_tree.vue';
import ResizablePanel from './resizable_panel.vue';
export default { export default {
components: { components: {
...@@ -11,65 +12,27 @@ ...@@ -11,65 +12,27 @@
icon, icon,
panelResizer, panelResizer,
skeletonLoadingContainer, skeletonLoadingContainer,
}, ResizablePanel,
data() {
return {
width: 290,
};
}, },
computed: { computed: {
...mapState([ ...mapState([
'loading', 'loading',
'projects',
'leftPanelCollapsed',
]), ]),
currentIcon() { ...mapGetters([
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left'; 'projectsWithTrees',
},
maxSize() {
return window.innerWidth / 2;
},
panelStyle() {
if (!this.leftPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
},
showLoading() {
return this.loading;
},
},
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
]), ]),
toggleCollapsed() {
this.setPanelCollapsedStatus({
side: 'left',
collapsed: !this.leftPanelCollapsed,
});
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
}, },
}; };
</script> </script>
<template> <template>
<div <resizable-panel
class="multi-file-commit-panel" :collapsible="false"
:class="{ :initial-width="290"
'is-collapsed': leftPanelCollapsed, side="left"
}"
:style="panelStyle"
> >
<div class="multi-file-commit-panel-inner"> <div class="multi-file-commit-panel-inner">
<template v-if="showLoading"> <template v-if="loading">
<div <div
class="multi-file-loading-container" class="multi-file-loading-container"
v-for="n in 3" v-for="n in 3"
...@@ -79,36 +42,10 @@ ...@@ -79,36 +42,10 @@
</div> </div>
</template> </template>
<project-tree <project-tree
v-for="project in projects" v-for="project in projectsWithTrees"
:key="project.id" :key="project.id"
:project="project" :project="project"
/> />
</div> </div>
<button </resizable-panel>
type="button"
class="btn btn-transparent left-collapse-btn"
@click="toggleCollapsed"
>
<icon
:name="currentIcon"
:size="18"
/>
<span
v-if="!leftPanelCollapsed"
class="collapse-text"
>
Collapse sidebar
</span>
</button>
<panel-resizer
:size.sync="width"
:enabled="!leftPanelCollapsed"
:start-size="290"
:min-size="200"
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="right"
/>
</div>
</template> </template>
<script> <script>
import { mapState } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import timeAgoMixin from '~/vue_shared/mixins/timeago'; import timeAgoMixin from '~/vue_shared/mixins/timeago';
...@@ -20,11 +19,6 @@ ...@@ -20,11 +19,6 @@
required: true, required: true,
}, },
}, },
computed: {
...mapState([
'selectedFile',
]),
},
}; };
</script> </script>
...@@ -35,32 +29,32 @@ ...@@ -35,32 +29,32 @@
name="branch" name="branch"
:size="12" :size="12"
/> />
{{ selectedFile.branchId }} {{ file.branchId }}
</div> </div>
<div> <div>
<div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id"> <div v-if="file.lastCommit && file.lastCommit.id">
Last commit: Last commit:
<a <a
v-tooltip v-tooltip
:title="selectedFile.lastCommit.message" :title="file.lastCommit.message"
:href="selectedFile.lastCommit.url" :href="file.lastCommit.url"
> >
{{ timeFormated(selectedFile.lastCommit.updatedAt) }} by {{ timeFormated(file.lastCommit.updatedAt) }} by
{{ selectedFile.lastCommit.author }} {{ file.lastCommit.author }}
</a> </a>
</div> </div>
</div> </div>
<div class="text-right"> <div class="text-right">
{{ selectedFile.name }} {{ file.name }}
</div> </div>
<div class="text-right"> <div class="text-right">
{{ selectedFile.eol }} {{ file.eol }}
</div> </div>
<div class="text-right"> <div class="text-right">
{{ file.editorRow }}:{{ file.editorColumn }} {{ file.editorRow }}:{{ file.editorColumn }}
</div> </div>
<div class="text-right"> <div class="text-right">
{{ selectedFile.fileLanguage }} {{ file.fileLanguage }}
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import newModal from './modal.vue'; import newModal from './modal.vue';
import upload from './upload.vue'; import upload from './upload.vue';
...@@ -18,10 +19,6 @@ ...@@ -18,10 +19,6 @@
type: String, type: String,
required: true, required: true,
}, },
parent: {
type: Object,
default: null,
},
}, },
data() { data() {
return { return {
...@@ -31,6 +28,9 @@ ...@@ -31,6 +28,9 @@
}; };
}, },
methods: { methods: {
...mapActions([
'createTempEntry',
]),
createNewItem(type) { createNewItem(type) {
this.modalType = type; this.modalType = type;
this.openModal = true; this.openModal = true;
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
<upload <upload
:branch-id="branch" :branch-id="branch"
:path="path" :path="path"
:parent="parent" @create="createTempEntry"
/> />
</li> </li>
<li> <li>
...@@ -104,8 +104,8 @@ ...@@ -104,8 +104,8 @@
:type="modalType" :type="modalType"
:branch-id="branch" :branch-id="branch"
:path="path" :path="path"
:parent="parent"
@hide="hideModal" @hide="hideModal"
@create="createTempEntry"
/> />
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
import modal from '~/vue_shared/components/modal.vue'; import modal from '~/vue_shared/components/modal.vue';
...@@ -12,10 +11,6 @@ ...@@ -12,10 +11,6 @@
type: String, type: String,
required: true, required: true,
}, },
parent: {
type: Object,
default: null,
},
type: { type: {
type: String, type: String,
required: true, required: true,
...@@ -31,9 +26,6 @@ ...@@ -31,9 +26,6 @@
}; };
}, },
computed: { computed: {
...mapState([
'currentProjectId',
]),
modalTitle() { modalTitle() {
if (this.type === 'tree') { if (this.type === 'tree') {
return __('Create new directory'); return __('Create new directory');
...@@ -60,15 +52,10 @@ ...@@ -60,15 +52,10 @@
this.$refs.fieldName.focus(); this.$refs.fieldName.focus();
}, },
methods: { methods: {
...mapActions([
'createTempEntry',
]),
createEntryInStore() { createEntryInStore() {
this.createTempEntry({ this.$emit('create', {
projectId: this.currentProjectId,
branchId: this.branchId, branchId: this.branchId,
parent: this.parent, name: this.entryName,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type, type: this.type,
}); });
......
<script> <script>
import { mapActions, mapState } from 'vuex';
export default { export default {
props: { props: {
branchId: { branchId: {
type: String, type: String,
required: true, required: true,
}, },
parent: { path: {
type: Object, type: String,
default: null, required: false,
default: '',
}, },
}, },
computed: {
...mapState([
'trees',
'currentProjectId',
]),
},
mounted() { mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile); this.$refs.fileUpload.addEventListener('change', this.openFile);
}, },
...@@ -25,9 +18,6 @@ ...@@ -25,9 +18,6 @@
this.$refs.fileUpload.removeEventListener('change', this.openFile); this.$refs.fileUpload.removeEventListener('change', this.openFile);
}, },
methods: { methods: {
...mapActions([
'createTempEntry',
]),
createFile(target, file, isText) { createFile(target, file, isText) {
const { name } = file; const { name } = file;
let { result } = target; let { result } = target;
...@@ -36,11 +26,9 @@ ...@@ -36,11 +26,9 @@
result = result.split('base64,')[1]; result = result.split('base64,')[1];
} }
this.createTempEntry({ this.$emit('create', {
name, name: `${(this.path ? `${this.path}/` : '')}${name}`,
projectId: this.currentProjectId,
branchId: this.branchId, branchId: this.branchId,
parent: this.parent,
type: 'blob', type: 'blob',
content: result, content: result,
base64: !isText, base64: !isText,
......
...@@ -5,9 +5,8 @@ import icon from '~/vue_shared/components/icon.vue'; ...@@ -5,9 +5,8 @@ import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue'; import modal from '~/vue_shared/components/modal.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue';
import commitFilesList from './commit_sidebar/list.vue'; import commitFilesList from './commit_sidebar/list.vue';
import * as consts from '../stores/modules/commit/constants';
import * as consts from 'ee/ide/stores/modules/commit/constants'; // eslint-disable-line import/first import Actions from './commit_sidebar/actions.vue';
import Actions from 'ee/ide/components/commit_sidebar/actions.vue'; // eslint-disable-line import/first
export default { export default {
components: { components: {
...@@ -53,7 +52,6 @@ export default { ...@@ -53,7 +52,6 @@ export default {
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'getTreeData',
'setPanelCollapsedStatus', 'setPanelCollapsedStatus',
]), ]),
...mapActions('commit', [ ...mapActions('commit', [
......
<script> <script>
/* global monaco */ /* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import flash from '~/flash'; import flash from '~/flash';
import monacoLoader from '../monaco_loader'; import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor'; import Editor from '../lib/editor';
export default { export default {
props: {
file: {
type: Object,
required: true,
},
},
computed: { computed: {
...mapGetters([
'activeFile',
'activeFileExtension',
]),
...mapState([ ...mapState([
'leftPanelCollapsed', 'leftPanelCollapsed',
'rightPanelCollapsed', 'rightPanelCollapsed',
'panelResizing',
'viewer', 'viewer',
'delayViewerUpdated', 'delayViewerUpdated',
]), ]),
shouldHideEditor() { shouldHideEditor() {
return this.activeFile && this.activeFile.binary && !this.activeFile.raw; return this.file && this.file.binary && !this.file.raw;
}, },
}, },
watch: { watch: {
activeFile(oldVal, newVal) { file(oldVal, newVal) {
if (newVal && !newVal.active) { if (newVal.path !== this.file.path) {
this.initMonaco(); this.initMonaco();
} }
}, },
...@@ -34,11 +35,6 @@ export default { ...@@ -34,11 +35,6 @@ export default {
rightPanelCollapsed() { rightPanelCollapsed() {
this.editor.updateDimensions(); this.editor.updateDimensions();
}, },
panelResizing(isResizing) {
if (isResizing === false) {
this.editor.updateDimensions();
}
},
viewer() { viewer() {
this.createEditorInstance(); this.createEditorInstance();
}, },
...@@ -72,7 +68,7 @@ export default { ...@@ -72,7 +68,7 @@ export default {
this.editor.clearEditor(); this.editor.clearEditor();
this.getRawFileData(this.activeFile) this.getRawFileData(this.file)
.then(() => { .then(() => {
const viewerPromise = this.delayViewerUpdated ? this.updateViewer('editor') : Promise.resolve(); const viewerPromise = this.delayViewerUpdated ? this.updateViewer('editor') : Promise.resolve();
...@@ -101,9 +97,9 @@ export default { ...@@ -101,9 +97,9 @@ export default {
}); });
}, },
setupEditor() { setupEditor() {
if (!this.activeFile || !this.editor.instance) return; if (!this.file || !this.editor.instance) return;
this.model = this.editor.createModel(this.activeFile); this.model = this.editor.createModel(this.file);
this.editor.attachModel(this.model); this.editor.attachModel(this.model);
...@@ -112,7 +108,7 @@ export default { ...@@ -112,7 +108,7 @@ export default {
if (file.active) { if (file.active) {
this.changeFileContent({ this.changeFileContent({
file, path: file.path,
content: model.getModel().getValue(), content: model.getModel().getValue(),
}); });
} }
...@@ -127,8 +123,8 @@ export default { ...@@ -127,8 +123,8 @@ export default {
}); });
this.editor.setPosition({ this.editor.setPosition({
lineNumber: this.activeFile.editorRow, lineNumber: this.file.editorRow,
column: this.activeFile.editorColumn, column: this.file.editorColumn,
}); });
// Handle File Language // Handle File Language
...@@ -152,7 +148,7 @@ export default { ...@@ -152,7 +148,7 @@ export default {
> >
<div <div
v-if="shouldHideEditor" v-if="shouldHideEditor"
v-html="activeFile.html" v-html="file.html"
> >
</div> </div>
<div <div
......
<script>
import { mapActions } from 'vuex';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import router from '../ide_router';
import newDropdown from './new_dropdown/index.vue';
import fileStatusIcon from './repo_file_status_icon.vue';
import changedFileIcon from './changed_file_icon.vue';
export default {
name: 'RepoFile',
components: {
skeletonLoadingContainer,
newDropdown,
fileStatusIcon,
fileIcon,
changedFileIcon,
},
props: {
file: {
type: Object,
required: true,
},
level: {
type: Number,
required: true,
},
},
computed: {
isTree() {
return this.file.type === 'tree';
},
isBlob() {
return this.file.type === 'blob';
},
levelIndentation() {
return {
marginLeft: `${this.level * 16}px`,
};
},
fileClass() {
return {
'file-open': this.isBlob && this.file.opened,
'file-active': this.isBlob && this.file.active,
folder: this.isTree,
};
},
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView();
}
},
methods: {
...mapActions(['toggleTreeOpen', 'updateDelayViewerUpdated']),
clickFile() {
// Manual Action if a tree is selected/opened
if (
this.isTree &&
this.$router.currentRoute.path === `/project${this.file.url}`
) {
this.toggleTreeOpen(this.file.path);
}
const delayPromise = this.file.changed
? Promise.resolve()
: this.updateDelayViewerUpdated(true);
return delayPromise.then(() => {
router.push(`/project${this.file.url}`);
});
},
},
};
</script>
<template>
<div>
<div
class="file"
:class="fileClass"
>
<div
class="file-name"
@click="clickFile"
role="button"
>
<span
class="ide-file-name str-truncated"
:style="levelIndentation"
>
<file-icon
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
:opened="file.opened"
:size="16"
/>
{{ file.name }}
<file-status-icon
:file="file"
/>
</span>
<changed-file-icon
:file="file"
v-if="file.changed || file.tempFile"
class="prepend-top-5 pull-right"
/>
<new-dropdown
v-if="isTree"
:project-id="file.projectId"
:branch="file.branchId"
:path="file.path"
class="pull-right prepend-left-8"
/>
</div>
</div>
<template v-if="file.opened">
<repo-file
v-for="childFile in file.tree"
:key="childFile.key"
:file="childFile"
:level="level + 1"
/>
</template>
</div>
</template>
<script> <script>
import { mapGetters } from 'vuex';
export default { export default {
props: {
file: {
type: Object,
required: true,
},
},
computed: { computed: {
...mapGetters([
'activeFile',
]),
showButtons() { showButtons() {
return this.activeFile.rawPath || return this.file.rawPath ||
this.activeFile.blamePath || this.file.blamePath ||
this.activeFile.commitsPath || this.file.commitsPath ||
this.activeFile.permalink; this.file.permalink;
}, },
rawDownloadButtonLabel() { rawDownloadButtonLabel() {
return this.activeFile.binary ? 'Download' : 'Raw'; return this.file.binary ? 'Download' : 'Raw';
}, },
}, },
}; };
...@@ -25,7 +26,7 @@ export default { ...@@ -25,7 +26,7 @@ export default {
class="multi-file-editor-btn-group" class="multi-file-editor-btn-group"
> >
<a <a
:href="activeFile.rawPath" :href="file.rawPath"
target="_blank" target="_blank"
class="btn btn-default btn-sm raw" class="btn btn-default btn-sm raw"
rel="noopener noreferrer"> rel="noopener noreferrer">
...@@ -38,19 +39,19 @@ export default { ...@@ -38,19 +39,19 @@ export default {
aria-label="File actions" aria-label="File actions"
> >
<a <a
:href="activeFile.blamePath" :href="file.blamePath"
class="btn btn-default btn-sm blame" class="btn btn-default btn-sm blame"
> >
Blame Blame
</a> </a>
<a <a
:href="activeFile.commitsPath" :href="file.commitsPath"
class="btn btn-default btn-sm history" class="btn btn-default btn-sm history"
> >
History History
</a> </a>
<a <a
:href="activeFile.permalink" :href="file.permalink"
class="btn btn-default btn-sm permalink" class="btn btn-default btn-sm permalink"
> >
Permalink Permalink
......
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
import fileIcon from '~/vue_shared/components/file_icon.vue'; import fileIcon from '~/vue_shared/components/file_icon.vue';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import fileStatusIcon from './repo_file_status_icon.vue';
import fileStatusIcon from 'ee/ide/components/repo_file_status_icon.vue'; import changedFileIcon from './changed_file_icon.vue';
import changedFileIcon from 'ee/ide/components/changed_file_icon.vue';
export default { export default {
components: { components: {
...@@ -67,7 +66,7 @@ ...@@ -67,7 +66,7 @@
<button <button
type="button" type="button"
class="multi-file-tab-close" class="multi-file-tab-close"
@click.stop.prevent="closeFile(tab)" @click.stop.prevent="closeFile(tab.path)"
:aria-label="closeLabel" :aria-label="closeLabel"
> >
<icon <icon
......
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions } from 'vuex';
import RepoTab from './repo_tab.vue'; import RepoTab from './repo_tab.vue';
import EditorMode from './editor_mode_dropdown.vue'; import EditorMode from './editor_mode_dropdown.vue';
...@@ -8,29 +8,33 @@ ...@@ -8,29 +8,33 @@
RepoTab, RepoTab,
EditorMode, EditorMode,
}, },
props: {
files: {
type: Array,
required: true,
},
viewer: {
type: String,
required: true,
},
hasChanges: {
type: Boolean,
required: true,
},
},
data() { data() {
return { return {
showShadow: false, showShadow: false,
}; };
}, },
computed: {
...mapGetters([
'hasChanges',
]),
...mapState([
'openFiles',
'viewer',
]),
},
updated() { updated() {
if (!this.$refs.tabsScroller) return; if (!this.$refs.tabsScroller) return;
this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth; this.showShadow =
this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
}, },
methods: { methods: {
...mapActions([ ...mapActions(['updateViewer']),
'updateViewer',
]),
}, },
}; };
</script> </script>
...@@ -42,7 +46,7 @@ ...@@ -42,7 +46,7 @@
ref="tabsScroller" ref="tabsScroller"
> >
<repo-tab <repo-tab
v-for="tab in openFiles" v-for="tab in files"
:key="tab.key" :key="tab.key"
:tab="tab" :tab="tab"
/> />
......
<script>
import { mapActions, mapState } from 'vuex';
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
export default {
components: {
PanelResizer,
},
props: {
collapsible: {
type: Boolean,
required: true,
},
initialWidth: {
type: Number,
required: true,
},
minSize: {
type: Number,
required: false,
default: 200,
},
side: {
type: String,
required: true,
},
},
data() {
return {
width: this.initialWidth,
};
},
computed: {
...mapState({
collapsed(state) {
return state[`${this.side}PanelCollapsed`];
},
}),
panelStyle() {
if (!this.collapsed) {
return {
width: `${this.width}px`,
};
}
return {};
},
},
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
]),
toggleFullbarCollapsed() {
if (this.collapsed && this.collapsible) {
this.setPanelCollapsedStatus({
side: this.side,
collapsed: !this.collapsed,
});
}
},
},
maxSize: (window.innerWidth / 2),
};
</script>
<template>
<div
class="multi-file-commit-panel"
:class="{
'is-collapsed': collapsed && collapsible,
}"
:style="panelStyle"
@click="toggleFullbarCollapsed"
>
<slot></slot>
<panel-resizer
:size.sync="width"
:enabled="!collapsed"
:start-size="initialWidth"
:min-size="minSize"
:max-size="$options.maxSize"
@resize-start="setResizingStatus(true)"
@resize-end="setResizingStatus(false)"
:side="side === 'right' ? 'left' : 'right'"
/>
</div>
</template>
...@@ -2,9 +2,6 @@ import Vue from 'vue'; ...@@ -2,9 +2,6 @@ import Vue from 'vue';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import flash from '~/flash'; import flash from '~/flash';
import store from './stores'; import store from './stores';
import {
getTreeEntry,
} from './stores/utils';
Vue.use(VueRouter); Vue.use(VueRouter);
...@@ -76,7 +73,7 @@ router.beforeEach((to, from, next) => { ...@@ -76,7 +73,7 @@ router.beforeEach((to, from, next) => {
}) })
.then(() => { .then(() => {
if (to.params[0]) { if (to.params[0]) {
const treeEntry = getTreeEntry(store, `${to.params.namespace}/${to.params.project}/${to.params.branch}`, to.params[0]); const treeEntry = store.state.entries[to.params[0]];
if (treeEntry) { if (treeEntry) {
store.dispatch('handleTreeEntryAction', treeEntry); store.dispatch('handleTreeEntryAction', treeEntry);
} }
......
/* global monaco */ /* global monaco */
import Disposable from './disposable'; import Disposable from './disposable';
import eventHub from '../../eventhub';
import eventHub from 'ee/ide/eventhub'; // eslint-disable-line import/first
export default class Model { export default class Model {
constructor(monaco, file) { constructor(monaco, file) {
...@@ -11,16 +10,16 @@ export default class Model { ...@@ -11,16 +10,16 @@ export default class Model {
this.content = file.content !== '' ? file.content : file.raw; this.content = file.content !== '' ? file.content : file.raw;
this.disposable.add( this.disposable.add(
this.originalModel = this.monaco.editor.createModel( (this.originalModel = this.monaco.editor.createModel(
this.file.raw, this.file.raw,
undefined, undefined,
new this.monaco.Uri(null, null, `original/${this.file.path}`), new this.monaco.Uri(null, null, `original/${this.file.path}`),
), )),
this.model = this.monaco.editor.createModel( (this.model = this.monaco.editor.createModel(
this.content, this.content,
undefined, undefined,
new this.monaco.Uri(null, null, this.file.path), new this.monaco.Uri(null, null, this.file.path),
), )),
); );
this.events = new Map(); this.events = new Map();
...@@ -29,7 +28,10 @@ export default class Model { ...@@ -29,7 +28,10 @@ export default class Model {
this.dispose = this.dispose.bind(this); this.dispose = this.dispose.bind(this);
eventHub.$on(`editor.update.model.dispose.${this.file.path}`, this.dispose); eventHub.$on(`editor.update.model.dispose.${this.file.path}`, this.dispose);
eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent); eventHub.$on(
`editor.update.model.content.${this.file.path}`,
this.updateContent,
);
} }
get url() { get url() {
...@@ -63,9 +65,7 @@ export default class Model { ...@@ -63,9 +65,7 @@ export default class Model {
onChange(cb) { onChange(cb) {
this.events.set( this.events.set(
this.path, this.path,
this.disposable.add( this.disposable.add(this.model.onDidChangeContent(e => cb(this, e))),
this.model.onDidChangeContent(e => cb(this, e)),
),
); );
} }
...@@ -78,7 +78,13 @@ export default class Model { ...@@ -78,7 +78,13 @@ export default class Model {
this.disposable.dispose(); this.disposable.dispose();
this.events.clear(); this.events.clear();
eventHub.$off(`editor.update.model.dispose.${this.file.path}`, this.dispose); eventHub.$off(
eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent); `editor.update.model.dispose.${this.file.path}`,
this.dispose,
);
eventHub.$off(
`editor.update.model.content.${this.file.path}`,
this.updateContent,
);
} }
} }
import eventHub from 'ee/ide/eventhub'; import eventHub from '../../eventhub';
import Disposable from './disposable'; import Disposable from './disposable';
import Model from './model'; import Model from './model';
...@@ -26,7 +26,10 @@ export default class ModelManager { ...@@ -26,7 +26,10 @@ export default class ModelManager {
this.models.set(model.path, model); this.models.set(model.path, model);
this.disposable.add(model); this.disposable.add(model);
eventHub.$on(`editor.update.model.dispose.${file.path}`, this.removeCachedModel.bind(this, file)); eventHub.$on(
`editor.update.model.dispose.${file.path}`,
this.removeCachedModel.bind(this, file),
);
return model; return model;
} }
...@@ -34,7 +37,10 @@ export default class ModelManager { ...@@ -34,7 +37,10 @@ export default class ModelManager {
removeCachedModel(file) { removeCachedModel(file) {
this.models.delete(file.path); this.models.delete(file.path);
eventHub.$off(`editor.update.model.dispose.${file.path}`, this.removeCachedModel); eventHub.$off(
`editor.update.model.dispose.${file.path}`,
this.removeCachedModel,
);
} }
dispose() { dispose() {
......
import Vue from 'vue'; import Vue from 'vue';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import flash from '~/flash';
import * as types from './mutation_types'; import * as types from './mutation_types';
import FilesDecoratorWorker from './workers/files_decorator_worker';
export const redirectToUrl = (_, url) => visitUrl(url); export const redirectToUrl = (_, url) => visitUrl(url);
...@@ -8,11 +10,11 @@ export const setInitialData = ({ commit }, data) => ...@@ -8,11 +10,11 @@ export const setInitialData = ({ commit }, data) =>
commit(types.SET_INITIAL_DATA, data); commit(types.SET_INITIAL_DATA, data);
export const discardAllChanges = ({ state, commit, dispatch }) => { export const discardAllChanges = ({ state, commit, dispatch }) => {
state.changedFiles.forEach((file) => { state.changedFiles.forEach(file => {
commit(types.DISCARD_FILE_CHANGES, file); commit(types.DISCARD_FILE_CHANGES, file.path);
if (file.tempFile) { if (file.tempFile) {
dispatch('closeFile', file); dispatch('closeFile', file.path);
} }
}); });
...@@ -20,20 +22,7 @@ export const discardAllChanges = ({ state, commit, dispatch }) => { ...@@ -20,20 +22,7 @@ export const discardAllChanges = ({ state, commit, dispatch }) => {
}; };
export const closeAllFiles = ({ state, dispatch }) => { export const closeAllFiles = ({ state, dispatch }) => {
state.openFiles.forEach(file => dispatch('closeFile', file)); state.openFiles.forEach(file => dispatch('closeFile', file.path));
};
export const toggleEditMode = ({ commit, dispatch }) => {
commit(types.TOGGLE_EDIT_MODE);
dispatch('toggleBlobView');
};
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 }) => { export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
...@@ -49,28 +38,63 @@ export const setResizingStatus = ({ commit }, resizing) => { ...@@ -49,28 +38,63 @@ export const setResizingStatus = ({ commit }, resizing) => {
}; };
export const createTempEntry = ( export const createTempEntry = (
{ state, dispatch }, { state, commit, dispatch },
{ projectId, branchId, parent, name, type, content = '', base64 = false }, { branchId, name, type, content = '', base64 = false },
) => { ) =>
const selectedParent = parent || state.trees[`${projectId}/${branchId}`]; new Promise(resolve => {
if (type === 'tree') { const worker = new FilesDecoratorWorker();
dispatch('createTempTree', { const fullName =
projectId, name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
branchId,
parent: selectedParent, if (state.entries[name]) {
name, flash(
`The name "${name
.split('/')
.pop()}" is already taken in this directory.`,
'alert',
document,
null,
false,
true,
);
resolve();
return null;
}
worker.addEventListener('message', ({ data }) => {
const { file } = data;
worker.terminate();
commit(types.CREATE_TMP_ENTRY, {
data,
projectId: state.currentProjectId,
branchId,
});
if (type === 'blob') {
commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.ADD_FILE_TO_CHANGED, file.path);
dispatch('setFileActive', file.path);
}
resolve(file);
}); });
} else if (type === 'blob') {
dispatch('createTempFile', { worker.postMessage({
projectId, data: [fullName],
projectId: state.currentProjectId,
branchId, branchId,
parent: selectedParent, type,
name, tempFile: true,
base64, base64,
content, content,
}); });
}
}; return null;
});
export const scrollToTab = () => { export const scrollToTab = () => {
Vue.nextTick(() => { Vue.nextTick(() => {
...@@ -95,4 +119,3 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { ...@@ -95,4 +119,3 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
export * from './actions/tree'; export * from './actions/tree';
export * from './actions/file'; export * from './actions/file';
export * from './actions/project'; export * from './actions/project';
export * from './actions/branch';
import { normalizeHeaders } from '~/lib/utils/common_utils'; import { normalizeHeaders } from '~/lib/utils/common_utils';
import flash from '~/flash'; import flash from '~/flash';
import eventHub from 'ee/ide/eventhub'; import eventHub from '../../eventhub';
import service from '../../services'; import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import router from '../../ide_router'; import router from '../../ide_router';
import { import { setPageTitle } from '../utils';
findEntry,
setPageTitle, export const closeFile = ({ commit, state, getters, dispatch }, path) => {
createTemp, const indexOfClosedFile = state.openFiles.findIndex(f => f.path === path);
findIndexOfFile, const file = state.entries[path];
} from '../utils';
export const closeFile = ({ commit, state, dispatch }, file) => {
const indexOfClosedFile = findIndexOfFile(state.openFiles, file);
const fileWasActive = file.active; const fileWasActive = file.active;
commit(types.TOGGLE_FILE_OPEN, file); commit(types.TOGGLE_FILE_OPEN, path);
commit(types.SET_FILE_ACTIVE, { file, active: false }); commit(types.SET_FILE_ACTIVE, { path, active: false });
if (state.openFiles.length > 0 && fileWasActive) { if (state.openFiles.length > 0 && fileWasActive) {
const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1; const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
const nextFileToOpen = state.openFiles[nextIndexToOpen]; const nextFileToOpen = state.entries[state.openFiles[nextIndexToOpen].path];
router.push(`/project${nextFileToOpen.url}`); router.push(`/project${nextFileToOpen.url}`);
} else if (!state.openFiles.length) { } else if (!state.openFiles.length) {
router.push(`/project/${file.projectId}/tree/${file.branchId}/`); router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
} }
dispatch('getLastCommitData');
eventHub.$emit(`editor.update.model.dispose.${file.path}`); eventHub.$emit(`editor.update.model.dispose.${file.path}`);
}; };
export const setFileActive = ({ commit, state, getters, dispatch }, file) => { export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
const file = state.entries[path];
const currentActiveFile = getters.activeFile; const currentActiveFile = getters.activeFile;
if (file.active) return; if (file.active) return;
if (currentActiveFile) { if (currentActiveFile) {
commit(types.SET_FILE_ACTIVE, { file: currentActiveFile, active: false }); commit(types.SET_FILE_ACTIVE, {
path: currentActiveFile.path,
active: false,
});
} }
commit(types.SET_FILE_ACTIVE, { file, active: true }); commit(types.SET_FILE_ACTIVE, { path, active: true });
dispatch('scrollToTab'); dispatch('scrollToTab');
// reset hash for line highlighting
location.hash = '';
commit(types.SET_CURRENT_PROJECT, file.projectId); commit(types.SET_CURRENT_PROJECT, file.projectId);
commit(types.SET_CURRENT_BRANCH, file.branchId); commit(types.SET_CURRENT_BRANCH, file.branchId);
}; };
...@@ -54,104 +49,97 @@ export const setFileActive = ({ commit, state, getters, dispatch }, file) => { ...@@ -54,104 +49,97 @@ export const setFileActive = ({ commit, state, getters, dispatch }, file) => {
export const getFileData = ({ state, commit, dispatch }, file) => { export const getFileData = ({ state, commit, dispatch }, file) => {
commit(types.TOGGLE_LOADING, { entry: file }); commit(types.TOGGLE_LOADING, { entry: file });
service.getFileData(file.url) return service
.then((res) => { .getFileData(file.url)
.then(res => {
const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
setPageTitle(pageTitle); setPageTitle(pageTitle);
return res.json(); return res.json();
}) })
.then((data) => { .then(data => {
commit(types.SET_FILE_DATA, { data, file }); commit(types.SET_FILE_DATA, { data, file });
commit(types.TOGGLE_FILE_OPEN, file); commit(types.TOGGLE_FILE_OPEN, file.path);
dispatch('setFileActive', file); dispatch('setFileActive', file.path);
commit(types.TOGGLE_LOADING, { entry: file }); commit(types.TOGGLE_LOADING, { entry: file });
}) })
.catch(() => { .catch(() => {
commit(types.TOGGLE_LOADING, { entry: file }); commit(types.TOGGLE_LOADING, { entry: file });
flash('Error loading file data. Please try again.', 'alert', document, null, false, true); flash(
'Error loading file data. Please try again.',
'alert',
document,
null,
false,
true,
);
}); });
}; };
export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file) export const getRawFileData = ({ commit, dispatch }, file) =>
.then((raw) => { service
commit(types.SET_FILE_RAW_DATA, { file, raw }); .getRawFileData(file)
}) .then(raw => {
.catch(() => flash('Error loading file content. Please try again.', 'alert', document, null, false, true)); commit(types.SET_FILE_RAW_DATA, { file, raw });
})
export const changeFileContent = ({ state, commit }, { file, content }) => { .catch(() =>
commit(types.UPDATE_FILE_CONTENT, { file, content }); flash(
'Error loading file content. Please try again.',
const indexOfChangedFile = findIndexOfFile(state.changedFiles, file); 'alert',
document,
null,
false,
true,
),
);
export const changeFileContent = ({ state, commit }, { path, content }) => {
const file = state.entries[path];
commit(types.UPDATE_FILE_CONTENT, { path, content });
const indexOfChangedFile = state.changedFiles.findIndex(f => f.path === path);
if (file.changed && indexOfChangedFile === -1) { if (file.changed && indexOfChangedFile === -1) {
commit(types.ADD_FILE_TO_CHANGED, file); commit(types.ADD_FILE_TO_CHANGED, path);
} else if (!file.changed && indexOfChangedFile !== -1) { } else if (!file.changed && indexOfChangedFile !== -1) {
commit(types.REMOVE_FILE_FROM_CHANGED, file); commit(types.REMOVE_FILE_FROM_CHANGED, path);
} }
}; };
export const setFileLanguage = ({ state, commit }, { fileLanguage }) => { export const setFileLanguage = ({ getters, commit }, { fileLanguage }) => {
if (state.selectedFile) { if (getters.activeFile) {
commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage }); commit(types.SET_FILE_LANGUAGE, { file: getters.activeFile, fileLanguage });
} }
}; };
export const setFileEOL = ({ state, commit }, { eol }) => { export const setFileEOL = ({ getters, commit }, { eol }) => {
if (state.selectedFile) { if (getters.activeFile) {
commit(types.SET_FILE_EOL, { file: state.selectedFile, eol }); commit(types.SET_FILE_EOL, { file: getters.activeFile, eol });
} }
}; };
export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => { export const setEditorPosition = (
if (state.selectedFile) { { getters, commit },
commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn }); { editorRow, editorColumn },
) => {
if (getters.activeFile) {
commit(types.SET_FILE_POSITION, {
file: getters.activeFile,
editorRow,
editorColumn,
});
} }
}; };
export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => { export const discardFileChanges = ({ state, commit }, path) => {
const path = parent.path !== undefined ? parent.path : ''; const file = state.entries[path];
// We need to do the replacement otherwise the web_url + file.url duplicate
const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`;
const file = createTemp({
projectId,
branchId,
name: name.replace(`${path}/`, ''),
path,
type: 'blob',
level: parent.level !== undefined ? parent.level + 1 : 0,
changed: true,
content,
base64,
url: newUrl,
});
if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`, 'alert', document, null, false, true);
commit(types.CREATE_TMP_FILE, {
parent,
file,
});
commit(types.TOGGLE_FILE_OPEN, file);
commit(types.ADD_FILE_TO_CHANGED, file);
dispatch('setFileActive', file);
if (!state.editMode && !file.base64) {
dispatch('toggleEditMode', true);
}
router.push(`/project${file.url}`);
return Promise.resolve(file);
};
export const discardFileChanges = ({ commit }, file) => { commit(types.DISCARD_FILE_CHANGES, path);
commit(types.DISCARD_FILE_CHANGES, file); commit(types.REMOVE_FILE_FROM_CHANGED, path);
commit(types.REMOVE_FILE_FROM_CHANGED, file);
if (file.tempFile && file.opened) { if (file.tempFile && file.opened) {
commit(types.TOGGLE_FILE_OPEN, file); commit(types.TOGGLE_FILE_OPEN, path);
} }
eventHub.$emit(`editor.update.model.content.${file.path}`, file.raw); eventHub.$emit(`editor.update.model.content.${file.path}`, file.raw);
......
...@@ -2,6 +2,29 @@ import flash from '~/flash'; ...@@ -2,6 +2,29 @@ import flash from '~/flash';
import service from '../../services'; import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
export const getProjectData = (
{ commit, state, dispatch },
{ namespace, projectId, force = false } = {},
) => new Promise((resolve, reject) => {
if (!state.projects[`${namespace}/${projectId}`] || force) {
commit(types.TOGGLE_LOADING, { entry: state });
service.getProjectData(namespace, projectId)
.then(res => res.data)
.then((data) => {
commit(types.TOGGLE_LOADING, { entry: state });
commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
resolve(data);
})
.catch(() => {
flash('Error loading project data. Please try again.', 'alert', document, null, false, true);
reject(new Error(`Project not loaded ${namespace}/${projectId}`));
});
} else {
resolve(state.projects[`${namespace}/${projectId}`]);
}
});
export const getBranchData = ( export const getBranchData = (
{ commit, state, dispatch }, { commit, state, dispatch },
{ projectId, branchId, force = false } = {}, { projectId, branchId, force = false } = {},
...@@ -24,20 +47,3 @@ export const getBranchData = ( ...@@ -24,20 +47,3 @@ export const getBranchData = (
resolve(state.projects[`${projectId}`].branches[branchId]); 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);
});
import { normalizeHeaders } from '~/lib/utils/common_utils';
import flash from '~/flash';
import service from '../../services';
import * as types from '../mutation_types';
import {
findEntry,
} from '../utils';
import FilesDecoratorWorker from '../workers/files_decorator_worker';
export const toggleTreeOpen = ({ commit, dispatch }, path) => {
commit(types.TOGGLE_TREE_OPEN, path);
};
export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
if (row.type === 'tree') {
dispatch('toggleTreeOpen', row.path);
} else if (row.type === 'blob' && (row.opened || row.changed)) {
if (row.changed && !row.opened) {
commit(types.TOGGLE_FILE_OPEN, row.path);
}
dispatch('setFileActive', row.path);
} else {
dispatch('getFileData', row);
}
};
export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
service.getTreeLastCommit(tree.lastCommitPath)
.then((res) => {
const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
return res.json();
})
.then((data) => {
data.forEach((lastCommit) => {
const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name);
if (entry) {
commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit });
}
});
dispatch('getLastCommitData', tree);
})
.catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
};
export const getFiles = (
{ state, commit, dispatch },
{ projectId, branchId } = {},
) => new Promise((resolve, reject) => {
if (!state.trees[`${projectId}/${branchId}`]) {
const selectedProject = state.projects[projectId];
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
service
.getFiles(selectedProject.web_url, branchId)
.then(res => res.json())
.then((data) => {
const worker = new FilesDecoratorWorker();
worker.addEventListener('message', (e) => {
const { entries, treeList } = e.data;
const selectedTree = state.trees[`${projectId}/${branchId}`];
commit(types.SET_ENTRIES, entries);
commit(types.SET_DIRECTORY_DATA, { treePath: `${projectId}/${branchId}`, data: treeList });
commit(types.TOGGLE_LOADING, { entry: selectedTree, forceValue: false });
worker.terminate();
resolve();
});
worker.postMessage({
data,
projectId,
branchId,
});
})
.catch((e) => {
flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
reject(e);
});
} else {
resolve();
}
});
export const activeFile = state =>
state.openFiles.find(file => file.active) || null;
export const addedFiles = state => state.changedFiles.filter(f => f.tempFile);
export const modifiedFiles = state =>
state.changedFiles.filter(f => !f.tempFile);
export const projectsWithTrees = state =>
Object.keys(state.projects).map(projectId => {
const project = state.projects[projectId];
return {
...project,
branches: Object.keys(project.branches).map(branchId => {
const branch = project.branches[branchId];
return {
...branch,
tree: state.trees[branch.treeId],
};
}),
};
});
// eslint-disable-next-line no-confusing-arrow
export const currentIcon = state =>
state.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
export const hasChanges = state => !!state.changedFiles.length;
...@@ -4,8 +4,7 @@ import state from './state'; ...@@ -4,8 +4,7 @@ import state from './state';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters'; import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
import commitModule from './modules/commit';
import commitModule from 'ee/ide/stores/modules/commit'; // eslint-disable-line import/first
Vue.use(Vuex); Vue.use(Vuex);
......
import $ from 'jquery'; import $ from 'jquery';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import * as rootTypes from 'ee/ide/stores/mutation_types';
import { createCommitPayload, createNewMergeRequestUrl } from 'ee/ide/stores/utils';
import router from 'ee/ide/ide_router';
import service from 'ee/ide/services';
import flash from '~/flash'; import flash from '~/flash';
import { stripHtml } from '~/lib/utils/text_utility'; import { stripHtml } from '~/lib/utils/text_utility';
import * as rootTypes from '../../mutation_types';
import { createCommitPayload, createNewMergeRequestUrl } from '../../utils';
import router from '../../../ide_router';
import service from '../../../services';
import * as types from './mutation_types'; import * as types from './mutation_types';
import * as consts from './constants'; import * as consts from './constants';
import eventHub from '../../../eventhub';
import eventHub from 'ee/ide/eventhub'; // eslint-disable-line import/first
export const updateCommitMessage = ({ commit }, message) => { export const updateCommitMessage = ({ commit }, message) => {
commit(types.UPDATE_COMMIT_MESSAGE, message); commit(types.UPDATE_COMMIT_MESSAGE, message);
...@@ -29,16 +28,18 @@ export const updateBranchName = ({ commit }, branchName) => { ...@@ -29,16 +28,18 @@ export const updateBranchName = ({ commit }, branchName) => {
export const setLastCommitMessage = ({ rootState, commit }, data) => { export const setLastCommitMessage = ({ rootState, commit }, data) => {
const currentProject = rootState.projects[rootState.currentProjectId]; const currentProject = rootState.projects[rootState.currentProjectId];
const commitStats = data.stats ? const commitStats = data.stats
sprintf( ? sprintf(__('with %{additions} additions, %{deletions} deletions.'), {
__('with %{additions} additions, %{deletions} deletions.'), additions: data.stats.additions, // eslint-disable-line indent
{ additions: data.stats.additions, deletions: data.stats.deletions }, deletions: data.stats.deletions, // eslint-disable-line indent
) }) // eslint-disable-line indent
: ''; : '';
const commitMsg = sprintf( const commitMsg = sprintf(
__('Your changes have been committed. Commit %{commitId} %{commitStats}'), __('Your changes have been committed. Commit %{commitId} %{commitStats}'),
{ {
commitId: `<a href="${currentProject.web_url}/commit/${data.short_id}" class="commit-sha">${data.short_id}</a>`, commitId: `<a href="${currentProject.web_url}/commit/${
data.short_id
}" class="commit-sha">${data.short_id}</a>`,
commitStats, commitStats,
}, },
false, false,
...@@ -53,7 +54,9 @@ export const checkCommitStatus = ({ rootState }) => ...@@ -53,7 +54,9 @@ export const checkCommitStatus = ({ rootState }) =>
.then(({ data }) => { .then(({ data }) => {
const { id } = data.commit; const { id } = data.commit;
const selectedBranch = const selectedBranch =
rootState.projects[rootState.currentProjectId].branches[rootState.currentBranchId]; rootState.projects[rootState.currentProjectId].branches[
rootState.currentBranchId
];
if (selectedBranch.workingReference !== id) { if (selectedBranch.workingReference !== id) {
return true; return true;
...@@ -61,7 +64,16 @@ export const checkCommitStatus = ({ rootState }) => ...@@ -61,7 +64,16 @@ export const checkCommitStatus = ({ rootState }) =>
return false; return false;
}) })
.catch(() => flash(__('Error checking branch data. Please try again.'), 'alert', document, null, false, true)); .catch(() =>
flash(
__('Error checking branch data. Please try again.'),
'alert',
document,
null,
false,
true,
),
);
export const updateFilesAfterCommit = ( export const updateFilesAfterCommit = (
{ commit, dispatch, state, rootState, rootGetters }, { commit, dispatch, state, rootState, rootGetters },
...@@ -78,88 +90,129 @@ export const updateFilesAfterCommit = ( ...@@ -78,88 +90,129 @@ export const updateFilesAfterCommit = (
}, },
}; };
commit(rootTypes.SET_BRANCH_WORKING_REFERENCE, { commit(
projectId: rootState.currentProjectId, rootTypes.SET_BRANCH_WORKING_REFERENCE,
branchId: rootState.currentBranchId, {
reference: data.id, projectId: rootState.currentProjectId,
}, { root: true }); branchId: rootState.currentBranchId,
reference: data.id,
},
{ root: true },
);
rootState.changedFiles.forEach((entry) => { rootState.changedFiles.forEach(entry => {
commit(rootTypes.SET_LAST_COMMIT_DATA, { commit(
entry, rootTypes.SET_LAST_COMMIT_DATA,
lastCommit, {
}, { root: true }); entry,
lastCommit,
},
{ root: true },
);
eventHub.$emit(`editor.update.model.content.${entry.path}`, entry.content); eventHub.$emit(`editor.update.model.content.${entry.path}`, entry.content);
commit(rootTypes.SET_FILE_RAW_DATA, { commit(
file: entry, rootTypes.SET_FILE_RAW_DATA,
raw: entry.content, {
}, { root: true }); file: entry,
raw: entry.content,
commit(rootTypes.TOGGLE_FILE_CHANGED, { },
file: entry, { root: true },
changed: false, );
}, { root: true });
commit(
rootTypes.TOGGLE_FILE_CHANGED,
{
file: entry,
changed: false,
},
{ root: true },
);
}); });
commit(rootTypes.REMOVE_ALL_CHANGES_FILES, null, { root: true }); commit(rootTypes.REMOVE_ALL_CHANGES_FILES, null, { root: true });
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) { if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) {
router.push(`/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`); router.push(
`/project/${rootState.currentProjectId}/blob/${branch}/${
rootGetters.activeFile.path
}`,
);
} }
dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH); dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH);
}; };
export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => { export const commitChanges = ({
commit,
state,
getters,
dispatch,
rootState,
}) => {
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH; const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
const payload = createCommitPayload(getters.branchName, newBranch, state, rootState); const payload = createCommitPayload(
const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus'); getters.branchName,
newBranch,
state,
rootState,
);
const getCommitStatus = newBranch
? Promise.resolve(false)
: dispatch('checkCommitStatus');
commit(types.UPDATE_LOADING, true); commit(types.UPDATE_LOADING, true);
return getCommitStatus.then(branchChanged => new Promise((resolve) => { return getCommitStatus
if (branchChanged) { .then(
// show the modal with a Bootstrap call branchChanged =>
$('#ide-create-branch-modal').modal('show'); new Promise(resolve => {
} else { if (branchChanged) {
resolve(); // show the modal with a Bootstrap call
} $('#ide-create-branch-modal').modal('show');
})) } else {
.then(() => service.commit(rootState.currentProjectId, payload)) resolve();
.then(({ data }) => { }
commit(types.UPDATE_LOADING, false); }),
)
if (!data.short_id) { .then(() => service.commit(rootState.currentProjectId, payload))
flash(data.message, 'alert', document, null, false, true); .then(({ data }) => {
return; commit(types.UPDATE_LOADING, false);
}
if (!data.short_id) {
dispatch('setLastCommitMessage', data); flash(data.message, 'alert', document, null, false, true);
return;
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) { }
dispatch(
'redirectToUrl', dispatch('setLastCommitMessage', data);
createNewMergeRequestUrl( dispatch('updateCommitMessage', '');
rootState.projects[rootState.currentProjectId].web_url,
getters.branchName, if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
rootState.currentBranchId, dispatch(
), 'redirectToUrl',
{ root: true }, createNewMergeRequestUrl(
); rootState.projects[rootState.currentProjectId].web_url,
} else { getters.branchName,
dispatch('updateFilesAfterCommit', { data, branch: getters.branchName }); rootState.currentBranchId,
} ),
}) { root: true },
.catch((err) => { );
let errMsg = __('Error committing changes. Please try again.'); } else {
if (err.response.data && err.response.data.message) { dispatch('updateFilesAfterCommit', {
errMsg += ` (${stripHtml(err.response.data.message)})`; data,
} branch: getters.branchName,
flash(errMsg, 'alert', document, null, false, true); });
window.dispatchEvent(new Event('resize')); }
})
commit(types.UPDATE_LOADING, false); .catch(err => {
}); let errMsg = __('Error committing changes. Please try again.');
if (err.response.data && err.response.data.message) {
errMsg += ` (${stripHtml(err.response.data.message)})`;
}
flash(errMsg, 'alert', document, null, false, true);
window.dispatchEvent(new Event('resize'));
commit(types.UPDATE_LOADING, false);
});
}; };
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA'; export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
export const TOGGLE_LOADING = 'TOGGLE_LOADING'; export const TOGGLE_LOADING = 'TOGGLE_LOADING';
export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL';
export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA'; export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG'; export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG';
export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED'; export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
...@@ -20,7 +19,6 @@ export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN'; ...@@ -20,7 +19,6 @@ export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
// Tree mutation types // Tree mutation types
export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA'; export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA';
export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN'; 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 SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL';
export const CREATE_TREE = 'CREATE_TREE'; export const CREATE_TREE = 'CREATE_TREE';
export const REMOVE_ALL_CHANGES_FILES = 'REMOVE_ALL_CHANGES_FILES'; export const REMOVE_ALL_CHANGES_FILES = 'REMOVE_ALL_CHANGES_FILES';
...@@ -35,17 +33,11 @@ export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE'; ...@@ -35,17 +33,11 @@ export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
export const SET_FILE_POSITION = 'SET_FILE_POSITION'; export const SET_FILE_POSITION = 'SET_FILE_POSITION';
export const SET_FILE_EOL = 'SET_FILE_EOL'; export const SET_FILE_EOL = 'SET_FILE_EOL';
export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES'; export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
export const CREATE_TMP_FILE = 'CREATE_TMP_FILE';
export const ADD_FILE_TO_CHANGED = 'ADD_FILE_TO_CHANGED'; export const ADD_FILE_TO_CHANGED = 'ADD_FILE_TO_CHANGED';
export const REMOVE_FILE_FROM_CHANGED = 'REMOVE_FILE_FROM_CHANGED'; export const REMOVE_FILE_FROM_CHANGED = 'REMOVE_FILE_FROM_CHANGED';
export const TOGGLE_FILE_CHANGED = 'TOGGLE_FILE_CHANGED'; export const TOGGLE_FILE_CHANGED = 'TOGGLE_FILE_CHANGED';
// 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 SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH'; export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH';
export const SET_ENTRIES = 'SET_ENTRIES';
export const CREATE_TMP_ENTRY = 'CREATE_TMP_ENTRY';
export const UPDATE_VIEWER = 'UPDATE_VIEWER'; export const UPDATE_VIEWER = 'UPDATE_VIEWER';
export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE'; export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
...@@ -8,25 +8,19 @@ export default { ...@@ -8,25 +8,19 @@ export default {
[types.SET_INITIAL_DATA](state, data) { [types.SET_INITIAL_DATA](state, data) {
Object.assign(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, forceValue = undefined }) { [types.TOGGLE_LOADING](state, { entry, forceValue = undefined }) {
Object.assign(entry, { if (entry.path) {
loading: forceValue !== undefined ? forceValue : !entry.loading, Object.assign(state.entries[entry.path], {
}); loading:
}, forceValue !== undefined
[types.TOGGLE_EDIT_MODE](state) { ? forceValue
Object.assign(state, { : !state.entries[entry.path].loading,
editMode: !state.editMode, });
}); } else {
Object.assign(entry, {
loading: forceValue !== undefined ? forceValue : !entry.loading,
});
}
}, },
[types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) { [types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) {
Object.assign(state, { Object.assign(state, {
...@@ -57,6 +51,44 @@ export default { ...@@ -57,6 +51,44 @@ export default {
lastCommitMsg, lastCommitMsg,
}); });
}, },
[types.SET_ENTRIES](state, entries) {
Object.assign(state, {
entries,
});
},
[types.CREATE_TMP_ENTRY](state, { data, projectId, branchId }) {
Object.keys(data.entries).reduce((acc, key) => {
const entry = data.entries[key];
const foundEntry = state.entries[key];
if (!foundEntry) {
Object.assign(state.entries, {
[key]: entry,
});
} else {
const tree = entry.tree.filter(
f => foundEntry.tree.find(e => e.path === f.path) === undefined,
);
Object.assign(foundEntry, {
tree: foundEntry.tree.concat(tree),
});
}
return acc.concat(key);
}, []);
const foundEntry = state.trees[`${projectId}/${branchId}`].tree.find(
e => e.path === data.treeList[0].path,
);
if (!foundEntry) {
Object.assign(state.trees[`${projectId}/${branchId}`], {
tree: state.trees[`${projectId}/${branchId}`].tree.concat(
data.treeList,
),
});
}
},
[types.UPDATE_VIEWER](state, viewer) { [types.UPDATE_VIEWER](state, viewer) {
Object.assign(state, { Object.assign(state, {
viewer, viewer,
......
...@@ -7,16 +7,14 @@ export default { ...@@ -7,16 +7,14 @@ export default {
}); });
}, },
[types.SET_BRANCH](state, { projectPath, branchName, branch }) { [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], { Object.assign(state.projects[projectPath], {
branches: { branches: {
[branchName]: branch, [branchName]: {
...branch,
treeId: `${projectPath}/${branchName}`,
active: true,
workingReference: '',
},
}, },
}); });
}, },
......
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import { findIndexOfFile } from '../utils';
export default { export default {
[types.SET_FILE_ACTIVE](state, { file, active }) { [types.SET_FILE_ACTIVE](state, { path, active }) {
Object.assign(file, { Object.assign(state.entries[path], {
active, active,
}); });
Object.assign(state, {
selectedFile: file,
});
}, },
[types.TOGGLE_FILE_OPEN](state, file) { [types.TOGGLE_FILE_OPEN](state, path) {
Object.assign(file, { Object.assign(state.entries[path], {
opened: !file.opened, opened: !state.entries[path].opened,
}); });
if (file.opened) { if (state.entries[path].opened) {
state.openFiles.push(file); state.openFiles.push(state.entries[path]);
} else { } else {
state.openFiles.splice(findIndexOfFile(state.openFiles, file), 1); Object.assign(state, {
openFiles: state.openFiles.filter(f => f.path !== path),
});
} }
}, },
[types.SET_FILE_DATA](state, { data, file }) { [types.SET_FILE_DATA](state, { data, file }) {
Object.assign(file, { Object.assign(state.entries[file.path], {
id: data.id, id: data.id,
blamePath: data.blame_path, blamePath: data.blame_path,
commitsPath: data.commits_path, commitsPath: data.commits_path,
...@@ -34,53 +31,52 @@ export default { ...@@ -34,53 +31,52 @@ export default {
}); });
}, },
[types.SET_FILE_RAW_DATA](state, { file, raw }) { [types.SET_FILE_RAW_DATA](state, { file, raw }) {
Object.assign(file, { Object.assign(state.entries[file.path], {
raw, raw,
}); });
}, },
[types.UPDATE_FILE_CONTENT](state, { file, content }) { [types.UPDATE_FILE_CONTENT](state, { path, content }) {
const changed = content !== file.raw; const changed = content !== state.entries[path].raw;
Object.assign(file, { Object.assign(state.entries[path], {
content, content,
changed, changed,
}); });
}, },
[types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) { [types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) {
Object.assign(file, { Object.assign(state.entries[file.path], {
fileLanguage, fileLanguage,
}); });
}, },
[types.SET_FILE_EOL](state, { file, eol }) { [types.SET_FILE_EOL](state, { file, eol }) {
Object.assign(file, { Object.assign(state.entries[file.path], {
eol, eol,
}); });
}, },
[types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) { [types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) {
Object.assign(file, { Object.assign(state.entries[file.path], {
editorRow, editorRow,
editorColumn, editorColumn,
}); });
}, },
[types.DISCARD_FILE_CHANGES](state, file) { [types.DISCARD_FILE_CHANGES](state, path) {
Object.assign(file, { Object.assign(state.entries[path], {
content: file.raw, content: state.entries[path].raw,
changed: false, changed: false,
}); });
}, },
[types.CREATE_TMP_FILE](state, { file, parent }) { [types.ADD_FILE_TO_CHANGED](state, path) {
parent.tree.push(file); Object.assign(state, {
}, changedFiles: state.changedFiles.concat(state.entries[path]),
[types.ADD_FILE_TO_CHANGED](state, file) { });
state.changedFiles.push(file);
}, },
[types.REMOVE_FILE_FROM_CHANGED](state, file) { [types.REMOVE_FILE_FROM_CHANGED](state, path) {
const indexOfChangedFile = findIndexOfFile(state.changedFiles, file); Object.assign(state, {
changedFiles: state.changedFiles.filter(f => f.path !== path),
state.changedFiles.splice(indexOfChangedFile, 1); });
}, },
[types.TOGGLE_FILE_CHANGED](state, { file, changed }) { [types.TOGGLE_FILE_CHANGED](state, { file, changed }) {
Object.assign(file, { Object.assign(state.entries[file.path], {
changed, changed,
}); });
}, },
......
import * as types from '../mutation_types'; import * as types from '../mutation_types';
export default { export default {
[types.TOGGLE_TREE_OPEN](state, tree) { [types.TOGGLE_TREE_OPEN](state, path) {
Object.assign(tree, { Object.assign(state.entries[path], {
opened: !tree.opened, opened: !state.entries[path].opened,
}); });
}, },
[types.CREATE_TREE](state, { treePath }) { [types.CREATE_TREE](state, { treePath }) {
...@@ -16,14 +16,13 @@ export default { ...@@ -16,14 +16,13 @@ export default {
}), }),
}); });
}, },
[types.SET_DIRECTORY_DATA](state, { data, tree }) { [types.SET_DIRECTORY_DATA](state, { data, treePath }) {
Object.assign(tree, {
tree: data,
});
},
[types.SET_PARENT_TREE_URL](state, url) {
Object.assign(state, { Object.assign(state, {
parentTreeUrl: url, trees: Object.assign(state.trees, {
[treePath]: {
tree: data,
},
}),
}); });
}, },
[types.SET_LAST_COMMIT_URL](state, { tree = state, url }) { [types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
...@@ -31,9 +30,6 @@ export default { ...@@ -31,9 +30,6 @@ export default {
lastCommitPath: url, lastCommitPath: url,
}); });
}, },
[types.CREATE_TMP_TREE](state, { parent, tmpEntry }) {
parent.tree.push(tmpEntry);
},
[types.REMOVE_ALL_CHANGES_FILES](state) { [types.REMOVE_ALL_CHANGES_FILES](state) {
Object.assign(state, { Object.assign(state, {
changedFiles: [], changedFiles: [],
......
export default () => ({ export default () => ({
canCommit: false,
currentProjectId: '', currentProjectId: '',
currentBranchId: '', currentBranchId: '',
currentBlobView: 'repo-editor',
changedFiles: [], changedFiles: [],
editMode: true,
endpoints: {}, endpoints: {},
isInitialRoot: false,
lastCommitMsg: '', lastCommitMsg: '',
lastCommitPath: '', lastCommitPath: '',
loading: false, loading: false,
onTopOfBranch: false,
openFiles: [], openFiles: [],
selectedFile: null,
path: '',
parentTreeUrl: '', parentTreeUrl: '',
trees: {}, trees: {},
projects: {}, projects: {},
leftPanelCollapsed: false, leftPanelCollapsed: false,
rightPanelCollapsed: false, rightPanelCollapsed: false,
panelResizing: false, panelResizing: false,
entries: {},
viewer: 'editor', viewer: 'editor',
delayViewerUpdated: false, delayViewerUpdated: false,
}); });
import _ from 'underscore';
export const dataStructure = () => ({ export const dataStructure = () => ({
id: '', id: '',
key: '', key: '',
...@@ -9,9 +7,7 @@ export const dataStructure = () => ({ ...@@ -9,9 +7,7 @@ export const dataStructure = () => ({
name: '', name: '',
url: '', url: '',
path: '', path: '',
level: 0,
tempFile: false, tempFile: false,
icon: '',
tree: [], tree: [],
loading: false, loading: false,
opened: false, opened: false,
...@@ -25,7 +21,6 @@ export const dataStructure = () => ({ ...@@ -25,7 +21,6 @@ export const dataStructure = () => ({
updatedAt: '', updatedAt: '',
author: '', author: '',
}, },
tree_url: '',
blamePath: '', blamePath: '',
commitsPath: '', commitsPath: '',
permalink: '', permalink: '',
...@@ -51,8 +46,6 @@ export const decorateData = (entity) => { ...@@ -51,8 +46,6 @@ export const decorateData = (entity) => {
type, type,
url, url,
name, name,
icon,
tree_url,
path, path,
renderError, renderError,
content = '', content = '',
...@@ -61,7 +54,6 @@ export const decorateData = (entity) => { ...@@ -61,7 +54,6 @@ export const decorateData = (entity) => {
opened = false, opened = false,
changed = false, changed = false,
parentTreeUrl = '', parentTreeUrl = '',
level = 0,
base64 = false, base64 = false,
file_lock, file_lock,
...@@ -77,11 +69,8 @@ export const decorateData = (entity) => { ...@@ -77,11 +69,8 @@ export const decorateData = (entity) => {
type, type,
name, name,
url, url,
tree_url,
path, path,
level,
tempFile, tempFile,
icon: `fa-${icon}`,
opened, opened,
active, active,
parentTreeUrl, parentTreeUrl,
...@@ -95,32 +84,6 @@ export const decorateData = (entity) => { ...@@ -95,32 +84,6 @@ export const decorateData = (entity) => {
}; };
}; };
/*
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, prop = 'name') => tree.find( export const findEntry = (tree, type, name, prop = 'name') => tree.find(
f => f.type === type && f[prop] === name, f => f.type === type && f[prop] === name,
); );
...@@ -131,63 +94,6 @@ export const setPageTitle = (title) => { ...@@ -131,63 +94,6 @@ export const setPageTitle = (title) => {
document.title = 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 = ({ projectId,
branchId,
entry,
type,
parentTreeUrl,
level,
state }) => {
if (state.changedFiles.length) {
const foundChangedFile = findEntry(state.changedFiles, type, entry.path, 'path');
if (foundChangedFile) {
return foundChangedFile;
}
}
if (state.openFiles.length) {
const foundOpenFile = findEntry(state.openFiles, type, entry.path, 'path');
if (foundOpenFile) {
return foundOpenFile;
}
}
return decorateData({
...entry,
projectId,
branchId,
type,
parentTreeUrl,
level,
});
};
export const createCommitPayload = (branch, newBranch, state, rootState) => ({ export const createCommitPayload = (branch, newBranch, state, rootState) => ({
branch, branch,
commit_message: state.commitMessage, commit_message: state.commitMessage,
...@@ -214,11 +120,6 @@ const sortTreesByTypeAndName = (a, b) => { ...@@ -214,11 +120,6 @@ const sortTreesByTypeAndName = (a, b) => {
return 0; return 0;
}; };
export const sortTree = (sortedTree) => { export const sortTree = sortedTree => sortedTree.map(entity => Object.assign(entity, {
sortedTree.forEach((el) => { tree: entity.tree.length ? sortTree(entity.tree) : [],
Object.assign(el, { })).sort(sortTreesByTypeAndName);
tree: el && el.tree ? sortTree(el.tree) : [],
});
});
return sortedTree.sort(sortTreesByTypeAndName);
};
import {
decorateData,
sortTree,
} from '../utils';
self.addEventListener('message', (e) => {
const { data, projectId, branchId, tempFile = false, content = '', base64 = false } = e.data;
const treeList = [];
let file;
const entries = data.reduce((acc, path) => {
const pathSplit = path.split('/');
const blobName = pathSplit.pop().trim();
if (pathSplit.length > 0) {
pathSplit.reduce((pathAcc, folderName) => {
const parentFolder = acc[pathAcc[pathAcc.length - 1]];
const folderPath = `${(parentFolder ? `${parentFolder.path}/` : '')}${folderName}`;
const foundEntry = acc[folderPath];
if (!foundEntry) {
const tree = decorateData({
projectId,
branchId,
id: folderPath,
name: folderName,
path: folderPath,
url: `/${projectId}/tree/${branchId}/${folderPath}`,
type: 'tree',
parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
tempFile,
changed: tempFile,
opened: tempFile,
});
Object.assign(acc, {
[folderPath]: tree,
});
if (parentFolder) {
parentFolder.tree.push(tree);
} else {
treeList.push(tree);
}
pathAcc.push(tree.path);
} else {
pathAcc.push(foundEntry.path);
}
return pathAcc;
}, []);
}
if (blobName !== '') {
const fileFolder = acc[pathSplit.join('/')];
file = decorateData({
projectId,
branchId,
id: path,
name: blobName,
path,
url: `/${projectId}/blob/${branchId}/${path}`,
type: 'blob',
parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
tempFile,
changed: tempFile,
content,
base64,
});
Object.assign(acc, {
[path]: file,
});
if (fileFolder) {
fileFolder.tree.push(file);
} else {
treeList.push(file);
}
}
return acc;
}, {});
self.postMessage({
entries,
treeList: sortTree(treeList),
file,
});
});
/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */ /* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
import $ from 'jquery'; import $ from 'jquery';
import { insertText } from '~/lib/utils/common_utils';
const textUtils = {}; function selectedText(text, textarea) {
textUtils.selectedText = function(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd); return text.substring(textarea.selectionStart, textarea.selectionEnd);
}; }
textUtils.lineBefore = function(text, textarea) { function lineBefore(text, textarea) {
var split; var split;
split = text.substring(0, textarea.selectionStart).trim().split('\n'); split = text.substring(0, textarea.selectionStart).trim().split('\n');
return split[split.length - 1]; return split[split.length - 1];
}; }
textUtils.lineAfter = function(text, textarea) { function lineAfter(text, textarea) {
return text.substring(textarea.selectionEnd).trim().split('\n')[0]; return text.substring(textarea.selectionEnd).trim().split('\n')[0];
}; }
textUtils.blockTagText = function(text, textArea, blockTag, selected) { function blockTagText(text, textArea, blockTag, selected) {
var lineAfter, lineBefore; const before = lineBefore(text, textArea);
lineBefore = this.lineBefore(text, textArea); const after = lineAfter(text, textArea);
lineAfter = this.lineAfter(text, textArea); if (before === blockTag && after === blockTag) {
if (lineBefore === blockTag && lineAfter === blockTag) {
// To remove the block tag we have to select the line before & after // To remove the block tag we have to select the line before & after
if (blockTag != null) { if (blockTag != null) {
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1); textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
...@@ -32,10 +29,30 @@ textUtils.blockTagText = function(text, textArea, blockTag, selected) { ...@@ -32,10 +29,30 @@ textUtils.blockTagText = function(text, textArea, blockTag, selected) {
} else { } else {
return blockTag + "\n" + selected + "\n" + blockTag; return blockTag + "\n" + selected + "\n" + blockTag;
} }
}; }
textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) { function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine; var pos;
if (!textArea.setSelectionRange) {
return;
}
if (textArea.selectionStart === textArea.selectionEnd) {
if (wrapped) {
pos = textArea.selectionStart - tag.length;
} else {
pos = textArea.selectionStart;
}
if (removedLastNewLine) {
pos -= 1;
}
return textArea.setSelectionRange(pos, pos);
}
}
export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap) {
var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
removedLastNewLine = false; removedLastNewLine = false;
removedFirstNewLine = false; removedFirstNewLine = false;
currentLineEmpty = false; currentLineEmpty = false;
...@@ -67,9 +84,9 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) { ...@@ -67,9 +84,9 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) { if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
if (blockTag != null && blockTag !== '') { if (blockTag != null && blockTag !== '') {
insertText = this.blockTagText(text, textArea, blockTag, selected); textToInsert = blockTagText(text, textArea, blockTag, selected);
} else { } else {
insertText = selectedSplit.map(function(val) { textToInsert = selectedSplit.map(function(val) {
if (val.indexOf(tag) === 0) { if (val.indexOf(tag) === 0) {
return "" + (val.replace(tag, '')); return "" + (val.replace(tag, ''));
} else { } else {
...@@ -78,78 +95,42 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) { ...@@ -78,78 +95,42 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
}).join('\n'); }).join('\n');
} }
} else { } else {
insertText = "" + startChar + tag + selected + (wrap ? tag : ' '); textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
} }
if (removedFirstNewLine) { if (removedFirstNewLine) {
insertText = '\n' + insertText; textToInsert = '\n' + textToInsert;
} }
if (removedLastNewLine) { if (removedLastNewLine) {
insertText += '\n'; textToInsert += '\n';
} }
if (document.queryCommandSupported('insertText')) { insertText(textArea, textToInsert);
inserted = document.execCommand('insertText', false, insertText); return moveCursor(textArea, tag, wrap, removedLastNewLine);
} }
if (!inserted) {
try {
document.execCommand("ms-beginUndoUnit");
} catch (error) {}
textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
try {
document.execCommand("ms-endUndoUnit");
} catch (error) {}
}
return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
};
textUtils.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) { function updateText(textArea, tag, blockTag, wrap) {
var pos;
if (!textArea.setSelectionRange) {
return;
}
if (textArea.selectionStart === textArea.selectionEnd) {
if (wrapped) {
pos = textArea.selectionStart - tag.length;
} else {
pos = textArea.selectionStart;
}
if (removedLastNewLine) {
pos -= 1;
}
return textArea.setSelectionRange(pos, pos);
}
};
textUtils.updateText = function(textArea, tag, blockTag, wrap) {
var $textArea, selected, text; var $textArea, selected, text;
$textArea = $(textArea); $textArea = $(textArea);
textArea = $textArea.get(0); textArea = $textArea.get(0);
text = $textArea.val(); text = $textArea.val();
selected = this.selectedText(text, textArea); selected = selectedText(text, textArea);
$textArea.focus(); $textArea.focus();
return this.insertText(textArea, text, tag, blockTag, selected, wrap); return insertMarkdownText(textArea, text, tag, blockTag, selected, wrap);
}; }
textUtils.init = function(form) { function replaceRange(s, start, end, substitute) {
var self; return s.substring(0, start) + substitute + s.substring(end);
self = this; }
export function addMarkdownListeners(form) {
return $('.js-md', form).off('click').on('click', function() { return $('.js-md', form).off('click').on('click', function() {
var $this; const $this = $(this);
$this = $(this); return updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
return self.updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
}); });
}; }
textUtils.removeListeners = function(form) { export function removeMarkdownListeners(form) {
return $('.js-md', form).off('click'); return $('.js-md', form).off('click');
}; }
textUtils.replaceRange = function(s, start, end, substitute) {
return s.substring(0, start) + substitute + s.substring(end);
};
export default textUtils;
/* eslint-disable import/first */ /* eslint-disable import/first */
/* global ConfirmDangerModal */
/* global $ */ /* global $ */
import jQuery from 'jquery'; import jQuery from 'jquery';
...@@ -21,7 +20,6 @@ import './behaviors/'; ...@@ -21,7 +20,6 @@ import './behaviors/';
// everything else // everything else
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import bp from './breakpoints'; import bp from './breakpoints';
import './confirm_danger_modal';
import Flash, { removeFlashClickListener } from './flash'; import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown'; import './gl_dropdown';
import initTodoToggle from './header'; import initTodoToggle from './header';
...@@ -32,7 +30,6 @@ import LazyLoader from './lazy_loader'; ...@@ -32,7 +30,6 @@ import LazyLoader from './lazy_loader';
import initLogoAnimation from './logo'; import initLogoAnimation from './logo';
import './milestone_select'; import './milestone_select';
import './projects_dropdown'; import './projects_dropdown';
import './render_gfm';
import initBreadcrumbs from './breadcrumb'; import initBreadcrumbs from './breadcrumb';
// EE-only scripts // EE-only scripts
...@@ -218,16 +215,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -218,16 +215,6 @@ document.addEventListener('DOMContentLoaded', () => {
$(document).trigger('toggle.comments'); $(document).trigger('toggle.comments');
}); });
$document.on('click', '.js-confirm-danger', (e) => {
const btn = $(e.target);
const form = btn.closest('form');
const text = btn.data('confirmDangerMessage');
e.preventDefault();
// eslint-disable-next-line no-new
new ConfirmDangerModal(form, text);
});
$document.on('breakpoint:change', (e, breakpoint) => { $document.on('breakpoint:change', (e, breakpoint) => {
if (breakpoint === 'sm' || breakpoint === 'xs') { if (breakpoint === 'sm' || breakpoint === 'xs') {
const $gutterIcon = $sidebarGutterToggle.find('i'); const $gutterIcon = $sidebarGutterToggle.find('i');
......
...@@ -73,6 +73,10 @@ ...@@ -73,6 +73,10 @@
type: String, type: String,
required: true, required: true,
}, },
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: { emptyUnableToConnectSvgPath: {
type: String, type: String,
required: true, required: true,
...@@ -188,6 +192,7 @@ ...@@ -188,6 +192,7 @@
:clusters-path="clustersPath" :clusters-path="clustersPath"
:empty-getting-started-svg-path="emptyGettingStartedSvgPath" :empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath" :empty-loading-svg-path="emptyLoadingSvgPath"
:empty-no-data-svg-path="emptyNoDataSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath" :empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
/> />
</template> </template>
...@@ -27,6 +27,10 @@ ...@@ -27,6 +27,10 @@
type: String, type: String,
required: true, required: true,
}, },
emptyNoDataSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: { emptyUnableToConnectSvgPath: {
type: String, type: String,
required: true, required: true,
...@@ -54,7 +58,7 @@ ...@@ -54,7 +58,7 @@
buttonPath: this.documentationPath, buttonPath: this.documentationPath,
}, },
noData: { noData: {
svgUrl: this.emptyUnableToConnectSvgPath, svgUrl: this.emptyNoDataSvgPath,
title: 'No data found', title: 'No data found',
description: `You are connected to the Prometheus server, but there is currently description: `You are connected to the Prometheus server, but there is currently
no data to display.`, no data to display.`,
......
...@@ -105,6 +105,9 @@ export default class Notes { ...@@ -105,6 +105,9 @@ export default class Notes {
this.basePollingInterval = 15000; this.basePollingInterval = 15000;
this.maxPollingSteps = 4; this.maxPollingSteps = 4;
this.$wrapperEl = hasVueMRDiscussionsCookie()
? $(document).find('.diffs')
: $(document);
this.cleanBinding(); this.cleanBinding();
this.addBinding(); this.addBinding();
this.setPollingInterval(); this.setPollingInterval();
...@@ -138,10 +141,6 @@ export default class Notes { ...@@ -138,10 +141,6 @@ export default class Notes {
} }
addBinding() { addBinding() {
this.$wrapperEl = hasVueMRDiscussionsCookie()
? $(document).find('.diffs')
: $(document);
// Edit note link // Edit note link
this.$wrapperEl.on('click', '.js-note-edit', this.showEditForm.bind(this)); this.$wrapperEl.on('click', '.js-note-edit', this.showEditForm.bind(this));
this.$wrapperEl.on('click', '.note-edit-cancel', this.cancelEdit); this.$wrapperEl.on('click', '.note-edit-cancel', this.cancelEdit);
...@@ -226,14 +225,9 @@ export default class Notes { ...@@ -226,14 +225,9 @@ export default class Notes {
$(window).on('hashchange', this.onHashChange); $(window).on('hashchange', this.onHashChange);
this.boundGetContent = this.getContent.bind(this); this.boundGetContent = this.getContent.bind(this);
document.addEventListener('refreshLegacyNotes', this.boundGetContent); document.addEventListener('refreshLegacyNotes', this.boundGetContent);
this.eventsBound = true;
} }
cleanBinding() { cleanBinding() {
if (!this.eventsBound) {
return;
}
this.$wrapperEl.off('click', '.js-note-edit'); this.$wrapperEl.off('click', '.js-note-edit');
this.$wrapperEl.off('click', '.note-edit-cancel'); this.$wrapperEl.off('click', '.note-edit-cancel');
this.$wrapperEl.off('click', '.js-note-delete'); this.$wrapperEl.off('click', '.js-note-delete');
......
import groupAvatar from '~/group_avatar'; import groupAvatar from '~/group_avatar';
import TransferDropdown from '~/groups/transfer_dropdown'; import TransferDropdown from '~/groups/transfer_dropdown';
import initConfirmDangerModal from '~/confirm_danger_modal';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
groupAvatar(); groupAvatar();
new TransferDropdown(); // eslint-disable-line no-new new TransferDropdown(); // eslint-disable-line no-new
initConfirmDangerModal();
}); });
/* eslint-disable no-new */ /* eslint-disable no-new */
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit'; import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
import ProjectNew from '../shared/project_new'; import ProjectNew from '../shared/project_new';
import projectAvatar from '../shared/project_avatar'; import projectAvatar from '../shared/project_avatar';
import initProjectPermissionsSettings from '../shared/permissions'; import initProjectPermissionsSettings from '../shared/permissions';
...@@ -12,4 +13,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -12,4 +13,5 @@ document.addEventListener('DOMContentLoaded', () => {
initSettingsPanels(); initSettingsPanels();
projectAvatar(); projectAvatar();
initProjectPermissionsSettings(); initProjectPermissionsSettings();
initConfirmDangerModal();
}); });
import $ from 'jquery';
import 'vendor/peek';
import 'vendor/peek.performance_bar';
import { getParameterValues } from './lib/utils/url_utility';
export default class PerformanceBar {
constructor(opts) {
if (!PerformanceBar.singleton) {
this.init(opts);
PerformanceBar.singleton = this;
}
return PerformanceBar.singleton;
}
init(opts) {
const $container = $(opts.container);
this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
this.$lineProfileModal = $('#modal-peek-line-profile');
this.initEventListeners();
this.showModalOnLoad();
}
initEventListeners() {
this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
$(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
}
showModalOnLoad() {
// When a lineprofiler query-string param is present, we show the line
// profiler modal upon page load
if (/lineprofiler/.test(window.location.search)) {
PerformanceBar.toggleModal(this.$lineProfileModal);
}
}
handleLineProfileLink(e) {
const lineProfilerParameter = getParameterValues('lineprofiler');
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
const shouldToggleModal = lineProfilerParameter.length > 0 &&
lineProfilerParameterRegex.test(e.currentTarget.href);
if (shouldToggleModal) {
e.preventDefault();
PerformanceBar.toggleModal(this.$lineProfileModal);
}
}
static toggleModal($modal) {
if ($modal.length) {
$modal.modal('toggle');
}
}
static toggleLineProfileFile(e) {
$(e.currentTarget).parents('.peek-rblineprof-file').find('.data').toggle();
}
}
<script>
import GlModal from '~/vue_shared/components/gl_modal.vue';
export default {
components: {
GlModal,
},
props: {
currentRequest: {
type: Object,
required: true,
},
metric: {
type: String,
required: true,
},
header: {
type: String,
required: true,
},
details: {
type: String,
required: true,
},
keys: {
type: Array,
required: true,
},
},
};
</script>
<template>
<div
:id="`peek-view-${metric}`"
class="view"
>
<button
:data-target="`#modal-peek-${metric}-details`"
class="btn-blank btn-link bold"
type="button"
data-toggle="modal"
>
<span
v-if="currentRequest.details"
class="bold"
>
{{ currentRequest.details[metric].duration }}
/
{{ currentRequest.details[metric].calls }}
</span>
</button>
<gl-modal
v-if="currentRequest.details"
:id="`modal-peek-${metric}-details`"
:header-title-text="header"
class="performance-bar-modal"
>
<table class="table">
<tr
v-for="(item, index) in currentRequest.details[metric][details]"
:key="index"
>
<td><strong>{{ item.duration }}ms</strong></td>
<td
v-for="key in keys"
:key="key"
>
{{ item[key] }}
</td>
</tr>
</table>
<div slot="footer">
</div>
</gl-modal>
{{ metric }}
</div>
</template>
<script>
import $ from 'jquery';
import PerformanceBarService from '../services/performance_bar_service';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue';
import upstreamPerformanceBar from './upstream_performance_bar.vue';
import Flash from '../../flash';
export default {
components: {
detailedMetric,
requestSelector,
simpleMetric,
upstreamPerformanceBar,
},
props: {
store: {
type: Object,
required: true,
},
env: {
type: String,
required: true,
},
requestId: {
type: String,
required: true,
},
peekUrl: {
type: String,
required: true,
},
profileUrl: {
type: String,
required: true,
},
},
detailedMetrics: [
{ metric: 'pg', header: 'SQL queries', details: 'queries', keys: ['sql'] },
{
metric: 'gitaly',
header: 'Gitaly calls',
details: 'details',
keys: ['feature', 'request'],
},
],
simpleMetrics: ['redis', 'sidekiq'],
data() {
return { currentRequestId: '' };
},
computed: {
requests() {
return this.store.requestsWithDetails();
},
currentRequest: {
get() {
return this.store.findRequest(this.currentRequestId);
},
set(requestId) {
this.currentRequestId = requestId;
},
},
initialRequest() {
return this.currentRequestId === this.requestId;
},
lineProfileModal() {
return $('#modal-peek-line-profile');
},
},
mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
this.loadRequestDetails(this.requestId, window.location.href);
this.currentRequest = this.requestId;
if (this.lineProfileModal.length) {
this.lineProfileModal.modal('toggle');
}
},
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
},
methods: {
loadRequestDetails(requestId, requestUrl) {
if (!this.store.canTrackRequest(requestUrl)) {
return;
}
this.store.addRequest(requestId, requestUrl);
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
this.store.addRequestDetails(requestId, res.data.data);
})
.catch(() =>
Flash(`Error getting performance bar results for ${requestId}`),
);
},
changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId;
},
},
};
</script>
<template>
<div
id="js-peek"
:class="env"
>
<request-selector
v-if="currentRequest"
:current-request="currentRequest"
:requests="requests"
@change-current-request="changeCurrentRequest"
/>
<div
id="peek-view-host"
class="view prepend-left-5"
>
<span
v-if="currentRequest && currentRequest.details"
class="current-host"
>
{{ currentRequest.details.host.hostname }}
</span>
</div>
<div
v-if="currentRequest"
class="wrapper"
>
<upstream-performance-bar
v-if="initialRequest && currentRequest.details"
/>
<detailed-metric
v-for="metric in $options.detailedMetrics"
:key="metric.metric"
:current-request="currentRequest"
:metric="metric.metric"
:header="metric.header"
:details="metric.details"
:keys="metric.keys"
/>
<div
v-if="initialRequest"
id="peek-view-rblineprof"
class="view"
>
<button
v-if="lineProfileModal.length"
class="btn-link btn-blank"
data-toggle="modal"
data-target="#modal-peek-line-profile"
>
profile
</button>
<a
v-else
:href="profileUrl"
>
profile
</a>
</div>
<simple-metric
v-for="metric in $options.simpleMetrics"
:current-request="currentRequest"
:key="metric"
:metric="metric"
/>
<div
id="peek-view-gc"
class="view"
>
<span
v-if="currentRequest.details"
class="bold"
>
<span title="Invoke Time">{{ currentRequest.details.gc.gc_time }}</span>ms
/
<span title="Invoke Count">{{ currentRequest.details.gc.invokes }}</span>
gc
</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
currentRequest: {
type: Object,
required: true,
},
requests: {
type: Array,
required: true,
},
},
data() {
return {
currentRequestId: this.currentRequest.id,
};
},
watch: {
currentRequestId(newRequestId) {
this.$emit('change-current-request', newRequestId);
},
},
methods: {
truncatedUrl(requestUrl) {
const components = requestUrl.replace(/\/$/, '').split('/');
let truncated = components[components.length - 1];
if (truncated.match(/^\d+$/)) {
truncated = `${components[components.length - 2]}/${truncated}`;
}
return truncated;
},
},
};
</script>
<template>
<div
id="peek-request-selector"
class="append-right-5 pull-right"
>
<select v-model="currentRequestId">
<option
v-for="request in requests"
:key="request.id"
:value="request.id"
>
{{ truncatedUrl(request.url) }}
</option>
</select>
</div>
</template>
<script>
export default {
props: {
currentRequest: {
type: Object,
required: true,
},
metric: {
type: String,
required: true,
},
},
};
</script>
<template>
<div
:id="`peek-view-${metric}`"
class="view"
>
<span
v-if="currentRequest.details"
class="bold"
>
{{ currentRequest.details[metric].duration }}
/
{{ currentRequest.details[metric].calls }}
</span>
{{ metric }}
</div>
</template>
<script>
export default {
mounted() {
const upstreamPerformanceBar = document
.getElementById('peek-view-performance-bar')
.cloneNode(true);
this.$refs.wrapper.appendChild(upstreamPerformanceBar);
},
};
</script>
<template>
<div
id="peek-view-performance-bar-vue"
class="view"
ref="wrapper"
></div>
</template>
import 'vendor/peek.performance_bar';
import Vue from 'vue';
import performanceBarApp from './components/performance_bar_app.vue';
import PerformanceBarStore from './stores/performance_bar_store';
export default () =>
new Vue({
el: '#js-peek',
components: {
performanceBarApp,
},
data() {
const performanceBarData = document.querySelector(this.$options.el)
.dataset;
const store = new PerformanceBarStore();
return {
store,
env: performanceBarData.env,
requestId: performanceBarData.requestId,
peekUrl: performanceBarData.peekUrl,
profileUrl: performanceBarData.profileUrl,
};
},
render(createElement) {
return createElement('performance-bar-app', {
props: {
store: this.store,
env: this.env,
requestId: this.requestId,
peekUrl: this.peekUrl,
profileUrl: this.profileUrl,
},
});
},
});
import axios from '../../lib/utils/axios_utils';
export default class PerformanceBarService {
static fetchRequestDetails(peekUrl, requestId) {
return axios.get(peekUrl, { params: { request_id: requestId } });
}
static registerInterceptor(peekUrl, callback) {
return axios.interceptors.response.use(response => {
const requestId = response.headers['x-request-id'];
const requestUrl = response.config.url;
if (requestUrl !== peekUrl && requestId) {
callback(requestId, requestUrl);
}
return response;
});
}
static removeInterceptor(interceptor) {
axios.interceptors.response.eject(interceptor);
}
}
export default class PerformanceBarStore {
constructor() {
this.requests = [];
}
addRequest(requestId, requestUrl, requestDetails) {
if (!this.findRequest(requestId)) {
this.requests.push({
id: requestId,
url: requestUrl,
details: requestDetails,
});
}
return this.requests;
}
findRequest(requestId) {
return this.requests.find(request => request.id === requestId);
}
addRequestDetails(requestId, requestDetails) {
const request = this.findRequest(requestId);
request.details = requestDetails;
return request;
}
requestsWithDetails() {
return this.requests.filter(request => request.details);
}
canTrackRequest(requestUrl) {
return (
this.requests.filter(request => request.url === requestUrl).length < 2
);
}
}
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
import $ from 'jquery'; import $ from 'jquery';
import Cookies from 'js-cookie';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import flash from '../flash'; import flash from '../flash';
...@@ -10,7 +9,6 @@ export default class Profile { ...@@ -10,7 +9,6 @@ export default class Profile {
constructor({ form } = {}) { constructor({ form } = {}) {
this.onSubmitForm = this.onSubmitForm.bind(this); this.onSubmitForm = this.onSubmitForm.bind(this);
this.form = form || $('.edit-user'); this.form = form || $('.edit-user');
this.newRepoActivated = Cookies.get('new_repo');
this.setRepoRadio(); this.setRepoRadio();
this.bindEvents(); this.bindEvents();
this.initAvatarGlCrop(); this.initAvatarGlCrop();
...@@ -23,21 +21,28 @@ export default class Profile { ...@@ -23,21 +21,28 @@ export default class Profile {
modalCrop: '.modal-profile-crop', modalCrop: '.modal-profile-crop',
pickImageEl: '.js-choose-user-avatar-button', pickImageEl: '.js-choose-user-avatar-button',
uploadImageBtn: '.js-upload-user-avatar', uploadImageBtn: '.js-upload-user-avatar',
modalCropImg: '.modal-profile-crop-image' modalCropImg: '.modal-profile-crop-image',
}; };
this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); this.avatarGlCrop = $('.js-user-avatar-input')
.glCrop(cropOpts)
.data('glcrop');
} }
bindEvents() { bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); $('.js-preferences-form').on(
$('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie); 'change.preference',
'input[type=radio]',
this.submitForm,
);
$('#user_notification_email').on('change', this.submitForm); $('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm);
this.form.on('submit', this.onSubmitForm); this.form.on('submit', this.onSubmitForm);
} }
submitForm() { submitForm() {
return $(this).parents('form').submit(); return $(this)
.parents('form')
.submit();
} }
onSubmitForm(e) { onSubmitForm(e) {
...@@ -59,21 +64,13 @@ export default class Profile { ...@@ -59,21 +64,13 @@ export default class Profile {
url: this.form.attr('action'), url: this.form.attr('action'),
data: formData, data: formData,
}) })
.then(({ data }) => flash(data.message, 'notice')) .then(({ data }) => flash(data.message, 'notice'))
.then(() => { .then(() => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
// Enable submit button after requests ends // Enable submit button after requests ends
self.form.find(':input[disabled]').enable(); self.form.find(':input[disabled]').enable();
}) })
.catch(error => flash(error.message)); .catch(error => flash(error.message));
}
setNewRepoCookie() {
if (this.value === 'off') {
Cookies.remove('new_repo');
} else {
Cookies.set('new_repo', true, { expires_in: 365 });
}
} }
setRepoRadio() { setRepoRadio() {
......
...@@ -3,7 +3,7 @@ import Mousetrap from 'mousetrap'; ...@@ -3,7 +3,7 @@ import Mousetrap from 'mousetrap';
import _ from 'underscore'; import _ from 'underscore';
import Sidebar from './right_sidebar'; import Sidebar from './right_sidebar';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
import { CopyAsGFM } from './behaviors/copy_as_gfm'; import { CopyAsGFM } from './behaviors/markdown/copy_as_gfm';
export default class ShortcutsIssuable extends Shortcuts { export default class ShortcutsIssuable extends Shortcuts {
constructor(isMergeRequest) { constructor(isMergeRequest) {
......
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetSHAMismatch',
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
The source branch HEAD has recently changed. Please reload the page and review the changes before merging
</span>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'ShaMismatch',
components: {
statusIcon,
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
The source branch HEAD has recently changed.
Please reload the page and review the changes before merging.
</span>
</div>
</div>
</template>
...@@ -28,7 +28,7 @@ export { default as NothingToMergeState } from './components/states/nothing_to_m ...@@ -28,7 +28,7 @@ export { default as NothingToMergeState } from './components/states/nothing_to_m
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge'; export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch'; export { default as ShaMismatchState } from './components/states/sha_mismatch.vue';
export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue'; export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
......
...@@ -19,7 +19,7 @@ import { ...@@ -19,7 +19,7 @@ import {
MissingBranchState, MissingBranchState,
NotAllowedState, NotAllowedState,
ReadyToMergeState, ReadyToMergeState,
SHAMismatchState, ShaMismatchState,
UnresolvedDiscussionsState, UnresolvedDiscussionsState,
PipelineBlockedState, PipelineBlockedState,
PipelineFailedState, PipelineFailedState,
...@@ -228,7 +228,7 @@ export default { ...@@ -228,7 +228,7 @@ export default {
'mr-widget-not-allowed': NotAllowedState, 'mr-widget-not-allowed': NotAllowedState,
'mr-widget-missing-branch': MissingBranchState, 'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState, 'mr-widget-ready-to-merge': ReadyToMergeState,
'mr-widget-sha-mismatch': SHAMismatchState, 'mr-widget-sha-mismatch': ShaMismatchState,
'mr-widget-squash-before-merge': SquashBeforeMerge, 'mr-widget-squash-before-merge': SquashBeforeMerge,
'mr-widget-checking': CheckingState, 'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState, 'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
......
...@@ -16,7 +16,7 @@ const stateToComponentMap = { ...@@ -16,7 +16,7 @@ const stateToComponentMap = {
mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds', mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds',
failedToMerge: 'mr-widget-failed-to-merge', failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed', autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'mr-widget-sha-mismatch', shaMismatch: 'sha-mismatch',
rebase: 'mr-widget-rebase', rebase: 'mr-widget-rebase',
}; };
......
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
padding-left: $contextual-sidebar-width; padding-left: $contextual-sidebar-width;
} }
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header { .issues-bulk-update.right-sidebar.right-sidebar-expanded
.issuable-sidebar-header {
padding: 10px 0 15px; padding: 10px 0 15px;
} }
} }
...@@ -61,7 +62,8 @@ ...@@ -61,7 +62,8 @@
} }
.nav-sidebar { .nav-sidebar {
transition: width $sidebar-transition-duration, left $sidebar-transition-duration; transition: width $sidebar-transition-duration,
left $sidebar-transition-duration;
position: fixed; position: fixed;
z-index: 400; z-index: 400;
width: $contextual-sidebar-width; width: $contextual-sidebar-width;
...@@ -75,7 +77,7 @@ ...@@ -75,7 +77,7 @@
&:not(.sidebar-collapsed-desktop) { &:not(.sidebar-collapsed-desktop) {
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
box-shadow: inset -2px 0 0 $border-color, box-shadow: inset -2px 0 0 $border-color,
2px 1px 3px $dropdown-shadow-color; 2px 1px 3px $dropdown-shadow-color;
} }
} }
...@@ -234,7 +236,7 @@ ...@@ -234,7 +236,7 @@
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
&::before { &::before {
content: ""; content: '';
position: absolute; position: absolute;
top: -30px; top: -30px;
bottom: -30px; bottom: -30px;
...@@ -305,7 +307,6 @@ ...@@ -305,7 +307,6 @@
} }
} }
// Collapsed nav // Collapsed nav
.toggle-sidebar-button, .toggle-sidebar-button,
...@@ -454,18 +455,3 @@ ...@@ -454,18 +455,3 @@
z-index: 300; z-index: 300;
} }
} }
// Make issue boards full-height now that sub-nav is gone
.boards-list {
height: calc(100vh - #{$header-height});
@media (min-width: $screen-sm-min) {
height: calc(100vh - 180px);
}
}
.with-performance-bar .boards-list {
height: calc(100vh - #{$header-height} - #{$performance-bar-height});
}
.navbar-gitlab { .navbar-gitlab {
&.navbar-gitlab { padding: 0 16px;
padding: 0 16px; z-index: 1000;
z-index: 1000; margin-bottom: 0;
margin-bottom: 0; min-height: $header-height;
min-height: $header-height; border: 0;
border: 0; border-bottom: 1px solid $border-color;
border-bottom: 1px solid $border-color; position: fixed;
position: fixed; top: 0;
top: 0; left: 0;
left: 0; right: 0;
right: 0; border-radius: 0;
border-radius: 0;
.logo-text {
.logo-text { line-height: initial;
line-height: initial;
svg {
svg { width: 55px;
width: 55px; height: 14px;
height: 14px; margin: 0;
margin: 0; fill: $white-light;
fill: $white-light;
}
}
.container-fluid {
padding: 0;
.user-counter {
svg {
margin-right: 3px;
}
}
.navbar-toggle {
right: -10px;
border-radius: 0;
min-width: 45px;
padding: 0;
margin-right: -7px;
font-size: 14px;
text-align: center;
color: currentColor;
&:hover,
&:focus,
&.active {
color: currentColor;
background-color: transparent;
}
.more-icon,
.close-icon {
fill: $white-light;
margin: auto;
}
}
} }
} }
...@@ -184,6 +148,37 @@ ...@@ -184,6 +148,37 @@
} }
.container-fluid { .container-fluid {
padding: 0;
.user-counter {
svg {
margin-right: 3px;
}
}
.navbar-toggle {
right: -10px;
border-radius: 0;
min-width: 45px;
padding: 0;
margin-right: -7px;
font-size: 14px;
text-align: center;
color: currentColor;
&:hover,
&:focus,
&.active {
color: currentColor;
background-color: transparent;
}
.more-icon,
.close-icon {
fill: $white-light;
margin: auto;
}
}
.navbar-nav { .navbar-nav {
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
...@@ -337,7 +332,7 @@ ...@@ -337,7 +332,7 @@
.breadcrumbs { .breadcrumbs {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
min-height: 48px; min-height: $breadcrumb-min-height;
color: $gl-text-color; color: $gl-text-color;
} }
...@@ -470,7 +465,7 @@ ...@@ -470,7 +465,7 @@
padding: 0 5px; padding: 0 5px;
line-height: 12px; line-height: 12px;
border-radius: 7px; border-radius: 7px;
box-shadow: 0 1px 0 rgba($gl-header-color, .2); box-shadow: 0 1px 0 rgba($gl-header-color, 0.2);
&.issues-count { &.issues-count {
background-color: $green-500; background-color: $green-500;
......
...@@ -5,9 +5,9 @@ $grid-size: 8px; ...@@ -5,9 +5,9 @@ $grid-size: 8px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 250px; $gutter_inner_width: 250px;
$sidebar-transition-duration: .3s; $sidebar-transition-duration: 0.3s;
$sidebar-breakpoint: 1024px; $sidebar-breakpoint: 1024px;
$default-transition-duration: .15s; $default-transition-duration: 0.15s;
$contextual-sidebar-width: 220px; $contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px; $contextual-sidebar-collapsed-width: 50px;
...@@ -130,7 +130,6 @@ $theme-green-800: #145d33; ...@@ -130,7 +130,6 @@ $theme-green-800: #145d33;
$theme-green-900: #0d4524; $theme-green-900: #0d4524;
$theme-green-950: #072d16; $theme-green-950: #072d16;
$black: #000; $black: #000;
$black-transparent: rgba(0, 0, 0, 0.3); $black-transparent: rgba(0, 0, 0, 0.3);
$almost-black: #242424; $almost-black: #242424;
...@@ -164,7 +163,7 @@ $gl-text-color-secondary: #707070; ...@@ -164,7 +163,7 @@ $gl-text-color-secondary: #707070;
$gl-text-color-tertiary: #949494; $gl-text-color-tertiary: #949494;
$gl-text-color-quaternary: #d6d6d6; $gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1); $gl-text-color-inverted: rgba(255, 255, 255, 1);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85); $gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85);
$gl-text-color-disabled: #919191; $gl-text-color-disabled: #919191;
$gl-text-green: $green-600; $gl-text-green: $green-600;
$gl-text-green-hover: $green-700; $gl-text-green-hover: $green-700;
...@@ -264,6 +263,7 @@ $highlight-changes-color: rgb(235, 255, 232); ...@@ -264,6 +263,7 @@ $highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px; $performance-bar-height: 35px;
$flash-height: 52px; $flash-height: 52px;
$context-header-height: 60px; $context-header-height: 60px;
$breadcrumb-min-height: 48px;
$issue-box-upcoming-bg: #8f8f8f; $issue-box-upcoming-bg: #8f8f8f;
$pages-group-name-color: #4c4e54; $pages-group-name-color: #4c4e54;
...@@ -302,7 +302,7 @@ $tanuki-yellow: #fca326; ...@@ -302,7 +302,7 @@ $tanuki-yellow: #fca326;
*/ */
$gl-primary: $blue-500; $gl-primary: $blue-500;
$gl-success: $green-500; $gl-success: $green-500;
$gl-success-focus: rgba($gl-success, .4); $gl-success-focus: rgba($gl-success, 0.4);
$gl-info: $blue-500; $gl-info: $blue-500;
$gl-warning: $orange-500; $gl-warning: $orange-500;
$gl-danger: $red-500; $gl-danger: $red-500;
...@@ -338,8 +338,11 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%); ...@@ -338,8 +338,11 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
/* /*
* Fonts * Fonts
*/ */
$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas',
$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
/* /*
* Dropdowns * Dropdowns
...@@ -350,16 +353,16 @@ $dropdown-max-height: 312px; ...@@ -350,16 +353,16 @@ $dropdown-max-height: 312px;
$dropdown-vertical-offset: 4px; $dropdown-vertical-offset: 4px;
$dropdown-link-color: #555; $dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover; $dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04); $dropdown-empty-row-bg: rgba(#000, 0.04);
$dropdown-border-color: $border-color; $dropdown-border-color: $border-color;
$dropdown-shadow-color: rgba(#000, .1); $dropdown-shadow-color: rgba(#000, 0.1);
$dropdown-divider-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, 0.1);
$dropdown-title-btn-color: #bfbfbf; $dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555; $dropdown-input-color: #555;
$dropdown-input-fa-color: #c7c7c7; $dropdown-input-fa-color: #c7c7c7;
$dropdown-input-focus-border: $focus-border-color; $dropdown-input-focus-border: $focus-border-color;
$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, 0.4);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, 0.6);
$dropdown-chevron-size: 10px; $dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%); $dropdown-toggle-active-border-color: darken($border-color, 14%);
$dropdown-item-hover-bg: $gray-darker; $dropdown-item-hover-bg: $gray-darker;
...@@ -374,9 +377,9 @@ $dropdown-hover-color: $blue-400; ...@@ -374,9 +377,9 @@ $dropdown-hover-color: $blue-400;
/* /*
* Contextual Sidebar * Contextual Sidebar
*/ */
$link-active-background: rgba(0, 0, 0, .04); $link-active-background: rgba(0, 0, 0, 0.04);
$link-hover-background: rgba(0, 0, 0, .06); $link-hover-background: rgba(0, 0, 0, 0.06);
$inactive-badge-background: rgba(0, 0, 0, .08); $inactive-badge-background: rgba(0, 0, 0, 0.08);
/* /*
* Buttons * Buttons
...@@ -404,14 +407,14 @@ $status-icon-margin: $gl-btn-padding; ...@@ -404,14 +407,14 @@ $status-icon-margin: $gl-btn-padding;
/* /*
* Award emoji * Award emoji
*/ */
$award-emoji-menu-shadow: rgba(0, 0, 0, .175); $award-emoji-menu-shadow: rgba(0, 0, 0, 0.175);
$award-emoji-positive-add-bg: #fed159; $award-emoji-positive-add-bg: #fed159;
$award-emoji-positive-add-lines: #bb9c13; $award-emoji-positive-add-lines: #bb9c13;
/* /*
* Search Box * Search Box
*/ */
$search-input-border-color: rgba($blue-400, .8); $search-input-border-color: rgba($blue-400, 0.8);
$search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: 220px; $search-input-width: 220px;
$location-badge-active-bg: $blue-500; $location-badge-active-bg: $blue-500;
...@@ -436,7 +439,7 @@ $zen-control-color: #555; ...@@ -436,7 +439,7 @@ $zen-control-color: #555;
* Calendar * Calendar
*/ */
$calendar-hover-bg: #ecf3fe; $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1); $calendar-border-color: rgba(#000, 0.1);
$calendar-user-contrib-text: #959494; $calendar-user-contrib-text: #959494;
/* /*
...@@ -459,6 +462,17 @@ $ci-skipped-color: #888; ...@@ -459,6 +462,17 @@ $ci-skipped-color: #888;
*/ */
$issue-boards-font-size: 14px; $issue-boards-font-size: 14px;
$issue-boards-card-shadow: rgba(186, 186, 186, 0.5); $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
/*
The following heights are used in boards.scss and are used for calculation of the board height.
They probably should be derived in a smarter way.
*/
$issue-boards-filter-height: 68px;
$issue-boards-breadcrumbs-height-xs: 63px;
$issue-board-list-difference-xs: $header-height +
$issue-boards-breadcrumbs-height-xs;
$issue-board-list-difference-sm: $header-height + $breadcrumb-min-height;
$issue-board-list-difference-md: $issue-board-list-difference-sm +
$issue-boards-filter-height;
/* /*
* Avatar * Avatar
...@@ -574,14 +588,14 @@ $label-padding: 7px; ...@@ -574,14 +588,14 @@ $label-padding: 7px;
$label-padding-modal: 10px; $label-padding-modal: 10px;
$label-gray-bg: #f8fafc; $label-gray-bg: #f8fafc;
$label-inverse-bg: #333; $label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1); $label-remove-border: rgba(0, 0, 0, 0.1);
$label-border-radius: 100px; $label-border-radius: 100px;
/* /*
* Animation * Animation
*/ */
$fade-in-duration: 200ms; $fade-in-duration: 200ms;
$fade-mask-transition-duration: .1s; $fade-mask-transition-duration: 0.1s;
$fade-mask-transition-curve: ease-in-out; $fade-mask-transition-curve: ease-in-out;
/* /*
...@@ -649,7 +663,6 @@ $stat-graph-selection-stroke: #333; ...@@ -649,7 +663,6 @@ $stat-graph-selection-stroke: #333;
$select2-drop-shadow1: rgba(76, 86, 103, 0.247059); $select2-drop-shadow1: rgba(76, 86, 103, 0.247059);
$select2-drop-shadow2: rgba(31, 37, 50, 0.317647); $select2-drop-shadow2: rgba(31, 37, 50, 0.317647);
/* /*
* Todo * Todo
*/ */
...@@ -686,7 +699,6 @@ CI variable lists ...@@ -686,7 +699,6 @@ CI variable lists
*/ */
$ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding}); $ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/* /*
Filtered Search Filtered Search
*/ */
...@@ -728,7 +740,14 @@ Repo editor ...@@ -728,7 +740,14 @@ Repo editor
*/ */
$repo-editor-grey: #f6f7f9; $repo-editor-grey: #f6f7f9;
$repo-editor-grey-darker: #e9ebee; $repo-editor-grey-darker: #e9ebee;
$repo-editor-linear-gradient: linear-gradient(to right, $repo-editor-grey 0%, $repo-editor-grey-darker, 20%, $repo-editor-grey 40%, $repo-editor-grey 100%); $repo-editor-linear-gradient: linear-gradient(
to right,
$repo-editor-grey 0%,
$repo-editor-grey-darker,
20%,
$repo-editor-grey 40%,
$repo-editor-grey 100%
);
/* /*
Performance Bar Performance Bar
...@@ -739,8 +758,8 @@ $perf-bar-staging: #291430; ...@@ -739,8 +758,8 @@ $perf-bar-staging: #291430;
$perf-bar-development: #4c1210; $perf-bar-development: #4c1210;
$perf-bar-bucket-bg: #111; $perf-bar-bucket-bg: #111;
$perf-bar-bucket-color: #ccc; $perf-bar-bucket-color: #ccc;
$perf-bar-bucket-box-shadow-from: rgba($white-light, .2); $perf-bar-bucket-box-shadow-from: rgba($white-light, 0.2);
$perf-bar-bucket-box-shadow-to: rgba($black, .25); $perf-bar-bucket-box-shadow-to: rgba($black, 0.25);
/* /*
Issuable warning Issuable warning
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment