Commit 5c25ea48 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'ide-sidebar-commit-box' into '44846-improve-web-ide-left-panel-and-modes'

Updated commit section in IDE sidebar

See merge request gitlab-org/gitlab-ce!18616
parents 9f2f8679 8a21c31b
...@@ -8,7 +8,7 @@ export default { ...@@ -8,7 +8,7 @@ export default {
Icon, Icon,
}, },
computed: { computed: {
...mapGetters(['currentProject']), ...mapGetters(['currentProject', 'hasChanges']),
...mapState(['currentActivityView']), ...mapState(['currentActivityView']),
goBackUrl() { goBackUrl() {
return document.referrer || this.currentProject.web_url; return document.referrer || this.currentProject.web_url;
...@@ -65,7 +65,7 @@ export default { ...@@ -65,7 +65,7 @@ export default {
/> />
</button> </button>
</li> </li>
<li> <li v-show="hasChanges">
<button <button
type="button" type="button"
class="ide-sidebar-link js-ide-commit-mode" class="ide-sidebar-link js-ide-commit-mode"
......
<script> <script>
import { mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import * as consts from '../../stores/modules/commit/constants'; import * as consts from '../../stores/modules/commit/constants';
import RadioGroup from './radio_group.vue'; import RadioGroup from './radio_group.vue';
...@@ -9,7 +9,7 @@ export default { ...@@ -9,7 +9,7 @@ export default {
RadioGroup, RadioGroup,
}, },
computed: { computed: {
...mapState(['currentBranchId']), ...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']),
commitToCurrentBranchText() { commitToCurrentBranchText() {
return sprintf( return sprintf(
__('Commit to %{branchName} branch'), __('Commit to %{branchName} branch'),
...@@ -17,6 +17,17 @@ export default { ...@@ -17,6 +17,17 @@ export default {
false, false,
); );
}, },
disableMergeRequestRadio() {
return this.changedFiles.length > 0 && this.stagedFiles.length > 0;
},
},
mounted() {
if (this.disableMergeRequestRadio) {
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
}
},
methods: {
...mapActions('commit', ['updateCommitAction']),
}, },
commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH, commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH, commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
...@@ -44,6 +55,7 @@ export default { ...@@ -44,6 +55,7 @@ export default {
:value="$options.commitToNewBranchMR" :value="$options.commitToNewBranchMR"
:label="__('Create a new branch and merge request')" :label="__('Create a new branch and merge request')"
:show-input="true" :show-input="true"
:disabled="disableMergeRequestRadio"
/> />
</div> </div>
</template> </template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import { sprintf, __ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import CommitMessageField from './message_field.vue';
import Actions from './actions.vue';
import SuccessMessage from './success_message.vue';
import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT, COMMIT_ITEM_PADDING } from '../../constants';
export default {
components: {
Actions,
LoadingButton,
CommitMessageField,
SuccessMessage,
},
data() {
return {
isCompact: true,
componentHeight: null,
};
},
computed: {
...mapState(['changedFiles', 'stagedFiles', 'currentActivityView', 'lastCommitMsg']),
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters(['hasChanges']),
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']),
overviewText() {
return sprintf(
__(
'<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes',
),
{
stagedFilesLength: this.stagedFiles.length,
changedFilesLength: this.changedFiles.length,
},
);
},
},
watch: {
currentActivityView() {
if (this.lastCommitMsg) {
this.isCompact = false;
} else {
this.isCompact = !(
this.currentActivityView === activityBarViews.commit &&
window.innerHeight >= MAX_WINDOW_HEIGHT_COMPACT
);
}
},
lastCommitMsg() {
this.isCompact =
this.currentActivityView !== activityBarViews.commit && this.lastCommitMsg === '';
},
},
methods: {
...mapActions(['updateActivityBarView']),
...mapActions('commit', ['updateCommitMessage', 'discardDraft', 'commitChanges']),
toggleIsSmall() {
this.updateActivityBarView(activityBarViews.commit)
.then(() => {
this.isCompact = !this.isCompact;
})
.catch(e => {
throw e;
});
},
beforeEnterTransition() {
const elHeight = this.isCompact
? this.$refs.formEl && this.$refs.formEl.offsetHeight
: this.$refs.compactEl && this.$refs.compactEl.offsetHeight;
this.componentHeight = elHeight + COMMIT_ITEM_PADDING;
},
enterTransition() {
this.$nextTick(() => {
const elHeight = this.isCompact
? this.$refs.compactEl && this.$refs.compactEl.offsetHeight
: this.$refs.formEl && this.$refs.formEl.offsetHeight;
this.componentHeight = elHeight + COMMIT_ITEM_PADDING;
});
},
afterEndTransition() {
this.componentHeight = null;
},
},
activityBarViews,
};
</script>
<template>
<div
class="multi-file-commit-form"
:class="{
'is-compact': isCompact,
'is-full': !isCompact
}"
:style="{
height: componentHeight ? `${componentHeight}px` : null,
}"
>
<transition
name="commit-form-slide-up"
@before-enter="beforeEnterTransition"
@enter="enterTransition"
@after-enter="afterEndTransition"
>
<div
v-if="isCompact"
class="commit-form-compact"
ref="compactEl"
>
<button
type="button"
:disabled="!hasChanges"
class="btn btn-primary btn-sm btn-block"
@click="toggleIsSmall"
>
{{ __('Commit') }}
</button>
<p
class="text-center"
v-html="overviewText"
></p>
</div>
<form
v-if="!isCompact"
class="form-horizontal"
@submit.prevent.stop="commitChanges"
ref="formEl"
>
<transition name="fade">
<success-message
v-show="lastCommitMsg"
/>
</transition>
<commit-message-field
:text="commitMessage"
@input="updateCommitMessage"
/>
<div class="clearfix prepend-top-15">
<actions />
<loading-button
:loading="submitCommitLoading"
:disabled="commitButtonDisabled"
container-class="btn btn-success btn-sm pull-left"
:label="__('Commit')"
@click="commitChanges"
/>
<button
v-if="!discardDraftButtonDisabled"
type="button"
class="btn btn-default btn-sm pull-right"
@click="discardDraft"
>
{{ __('Discard draft') }}
</button>
<button
v-else
type="button"
class="btn btn-default btn-sm pull-right"
@click="toggleIsSmall"
>
{{ __('Collapse') }}
</button>
</div>
</form>
</transition>
</div>
</template>
...@@ -44,6 +44,11 @@ export default { ...@@ -44,6 +44,11 @@ export default {
default: false, default: false,
}, },
}, },
data() {
return {
showActionButton: false,
};
},
computed: { computed: {
titleText() { titleText() {
return sprintf(__('%{title} changes'), { return sprintf(__('%{title} changes'), {
...@@ -56,6 +61,9 @@ export default { ...@@ -56,6 +61,9 @@ export default {
actionBtnClicked() { actionBtnClicked() {
this[this.action](); this[this.action]();
}, },
setShowActionButton(show) {
this.showActionButton = show;
},
}, },
}; };
</script> </script>
...@@ -76,7 +84,16 @@ export default { ...@@ -76,7 +84,16 @@ export default {
:size="18" :size="18"
/> />
{{ titleText }} {{ titleText }}
<span
v-show="!showActionButton"
@mouseenter="setShowActionButton(true)"
class="ide-commit-file-count"
>
{{ fileList.length }}
</span>
<button <button
v-show="showActionButton"
@mouseleave="setShowActionButton(false)"
type="button" type="button"
class="btn btn-blank btn-link ide-staged-action-btn" class="btn btn-blank btn-link ide-staged-action-btn"
@click="actionBtnClicked" @click="actionBtnClicked"
......
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
export default { export default {
...@@ -26,10 +27,20 @@ export default { ...@@ -26,10 +27,20 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
disabled: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
...mapState('commit', ['commitAction']), ...mapState('commit', ['commitAction']),
...mapGetters('commit', ['newBranchName']), ...mapGetters('commit', ['newBranchName']),
tooltipTitle() {
return this.disabled
? __('This option is disabled while you still have unstaged changes')
: '';
},
}, },
methods: { methods: {
...mapActions('commit', ['updateCommitAction', 'updateBranchName']), ...mapActions('commit', ['updateCommitAction', 'updateBranchName']),
...@@ -39,19 +50,28 @@ export default { ...@@ -39,19 +50,28 @@ export default {
<template> <template>
<fieldset> <fieldset>
<label> <label
v-tooltip
:title="tooltipTitle"
:class="{
'is-disabled': disabled
}"
>
<input <input
type="radio" type="radio"
name="commit-action" name="commit-action"
:value="value" :value="value"
@change="updateCommitAction($event.target.value)" @change="updateCommitAction($event.target.value)"
:checked="checked" :checked="commitAction === value"
v-once :disabled="disabled"
/> />
<span class="prepend-left-10"> <span class="prepend-left-10">
<template v-if="label"> <span
v-if="label"
class="ide-radio-label"
>
{{ label }} {{ label }}
</template> </span>
<slot v-else></slot> <slot v-else></slot>
</span> </span>
</label> </label>
......
...@@ -10,7 +10,10 @@ import IdeTree from './ide_tree.vue'; ...@@ -10,7 +10,10 @@ import IdeTree from './ide_tree.vue';
import ResizablePanel from './resizable_panel.vue'; import ResizablePanel from './resizable_panel.vue';
import ActivityBar from './activity_bar.vue'; import ActivityBar from './activity_bar.vue';
import CommitSection from './repo_commit_section.vue'; import CommitSection from './repo_commit_section.vue';
import CommitForm from './commit_sidebar/form.vue';
import IdeReview from './ide_review.vue'; import IdeReview from './ide_review.vue';
import SuccessMessage from './commit_sidebar/success_message.vue';
import { activityBarViews } from '../constants';
export default { export default {
directives: { directives: {
...@@ -26,7 +29,9 @@ export default { ...@@ -26,7 +29,9 @@ export default {
Identicon, Identicon,
CommitSection, CommitSection,
IdeTree, IdeTree,
CommitForm,
IdeReview, IdeReview,
SuccessMessage,
}, },
data() { data() {
return { return {
...@@ -34,8 +39,21 @@ export default { ...@@ -34,8 +39,21 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['loading', 'currentBranchId', 'currentActivityView']), ...mapState([
...mapGetters(['currentProject']), 'loading',
'currentBranchId',
'currentActivityView',
'changedFiles',
'stagedFiles',
'lastCommitMsg',
]),
...mapGetters(['currentProject', 'someUncommitedChanges']),
showSuccessMessage() {
return (
this.currentActivityView === activityBarViews.edit &&
(this.lastCommitMsg && !this.someUncommitedChanges)
);
},
branchTooltipTitle() { branchTooltipTitle() {
return this.showTooltip ? this.currentBranchId : undefined; return this.showTooltip ? this.currentBranchId : undefined;
}, },
...@@ -115,6 +133,7 @@ export default { ...@@ -115,6 +133,7 @@ export default {
:is="currentActivityView" :is="currentActivityView"
/> />
</div> </div>
<commit-form />
</template> </template>
</div> </div>
</resizable-panel> </resizable-panel>
......
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import NewDropdown from './new_dropdown/index.vue'; import NewDropdown from './new_dropdown/index.vue';
import IdeTreeList from './ide_tree_list.vue'; import IdeTreeList from './ide_tree_list.vue';
...@@ -10,7 +10,17 @@ export default { ...@@ -10,7 +10,17 @@ export default {
}, },
computed: { computed: {
...mapState(['currentBranchId']), ...mapState(['currentBranchId']),
...mapGetters(['currentProject']), ...mapGetters(['currentProject', 'currentTree', 'activeFile']),
},
mounted() {
if (this.activeFile && this.activeFile.pending) {
this.$router.push(`/project${this.activeFile.url}`, () => {
this.updateViewer('editor');
});
}
},
methods: {
...mapActions(['updateViewer']),
}, },
}; };
</script> </script>
......
...@@ -3,13 +3,10 @@ import { mapState, mapActions, mapGetters } from 'vuex'; ...@@ -3,13 +3,10 @@ import { mapState, mapActions, mapGetters } from 'vuex';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import DeprecatedModal from '~/vue_shared/components/deprecated_modal.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 EmptyState from './commit_sidebar/empty_state.vue'; import EmptyState from './commit_sidebar/empty_state.vue';
import CommitMessageField from './commit_sidebar/message_field.vue';
import SuccessMessage from './commit_sidebar/success_message.vue';
import * as consts from '../stores/modules/commit/constants'; import * as consts from '../stores/modules/commit/constants';
import Actions from './commit_sidebar/actions.vue'; import { activityBarViews } from '../constants';
export default { export default {
components: { components: {
...@@ -17,21 +14,11 @@ export default { ...@@ -17,21 +14,11 @@ export default {
Icon, Icon,
CommitFilesList, CommitFilesList,
EmptyState, EmptyState,
SuccessMessage,
Actions,
LoadingButton,
CommitMessageField,
}, },
directives: { directives: {
tooltip, tooltip,
}, },
computed: { computed: {
showStageUnstageArea() {
return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal);
},
someUncommitedChanges() {
return !!(this.changedFiles.length || this.stagedFiles.length);
},
...mapState([ ...mapState([
'changedFiles', 'changedFiles',
'stagedFiles', 'stagedFiles',
...@@ -40,15 +27,37 @@ export default { ...@@ -40,15 +27,37 @@ export default {
'unusedSeal', 'unusedSeal',
]), ]),
...mapState('commit', ['commitMessage', 'submitCommitLoading']), ...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges']),
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']), ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']),
showStageUnstageArea() {
return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal);
},
},
watch: {
hasChanges() {
if (!this.hasChanges) {
this.updateActivityBarView(activityBarViews.edit);
}
},
},
mounted() {
if (this.lastOpenedFile) {
this.openPendingTab({
file: this.lastOpenedFile,
})
.then(changeViewer => {
if (changeViewer) {
this.updateViewer('diff');
}
})
.catch(e => {
throw e;
});
}
}, },
methods: { methods: {
...mapActions('commit', [ ...mapActions(['openPendingTab', 'updateViewer', 'updateActivityBarView']),
'updateCommitMessage', ...mapActions('commit', ['commitChanges', 'updateCommitAction']),
'discardDraft',
'commitChanges',
'updateCommitAction',
]),
forceCreateNewBranch() { forceCreateNewBranch() {
return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => this.commitChanges()); return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => this.commitChanges());
}, },
...@@ -76,6 +85,7 @@ export default { ...@@ -76,6 +85,7 @@ export default {
v-if="showStageUnstageArea" v-if="showStageUnstageArea"
> >
<commit-files-list <commit-files-list
class="is-first"
icon-name="unstaged" icon-name="unstaged"
:title="__('Unstaged')" :title="__('Unstaged')"
:file-list="changedFiles" :file-list="changedFiles"
...@@ -96,39 +106,5 @@ export default { ...@@ -96,39 +106,5 @@ export default {
<empty-state <empty-state
v-if="unusedSeal" v-if="unusedSeal"
/> />
<div
class="multi-file-commit-panel-bottom"
>
<form
class="form-horizontal multi-file-commit-form"
@submit.prevent.stop="commitChanges"
>
<success-message
v-if="lastCommitMsg && !someUncommitedChanges"
/>
<commit-message-field
:text="commitMessage"
@input="updateCommitMessage"
/>
<div class="clearfix prepend-top-15">
<actions />
<loading-button
:loading="submitCommitLoading"
:disabled="commitButtonDisabled"
container-class="btn btn-success btn-sm pull-left"
:label="__('Commit')"
@click="commitChanges"
/>
<button
v-if="!discardDraftButtonDisabled"
type="button"
class="btn btn-default btn-sm pull-right"
@click="discardDraft"
>
{{ __('Discard draft') }}
</button>
</div>
</form>
</div>
</div> </div>
</template> </template>
...@@ -32,6 +32,8 @@ export default { ...@@ -32,6 +32,8 @@ export default {
return `Close ${this.tab.name}`; return `Close ${this.tab.name}`;
}, },
showChangedIcon() { showChangedIcon() {
if (this.tab.pending) return true;
return this.fileHasChanged ? !this.tabMouseOver : false; return this.fileHasChanged ? !this.tabMouseOver : false;
}, },
fileHasChanged() { fileHasChanged() {
...@@ -91,6 +93,7 @@ export default { ...@@ -91,6 +93,7 @@ export default {
class="multi-file-tab-close" class="multi-file-tab-close"
@click.stop.prevent="closeFile(tab)" @click.stop.prevent="closeFile(tab)"
:aria-label="closeLabel" :aria-label="closeLabel"
:disabled="tab.pending"
> >
<icon <icon
v-if="!showChangedIcon" v-if="!showChangedIcon"
......
...@@ -3,6 +3,10 @@ export const MAX_FILE_FINDER_RESULTS = 40; ...@@ -3,6 +3,10 @@ export const MAX_FILE_FINDER_RESULTS = 40;
export const FILE_FINDER_ROW_HEIGHT = 55; export const FILE_FINDER_ROW_HEIGHT = 55;
export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33;
export const MAX_WINDOW_HEIGHT_COMPACT = 750;
export const COMMIT_ITEM_PADDING = 32;
// Commit message textarea // Commit message textarea
export const MAX_TITLE_LENGTH = 50; export const MAX_TITLE_LENGTH = 50;
export const MAX_BODY_LENGTH = 72; export const MAX_BODY_LENGTH = 72;
......
...@@ -195,13 +195,7 @@ export const unstageChange = ({ commit }, path) => { ...@@ -195,13 +195,7 @@ export const unstageChange = ({ commit }, path) => {
}; };
export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => { export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => {
if ( state.openFiles.forEach(f => eventHub.$emit(`editor.update.model.dispose.${f.key}`));
getters.activeFile &&
getters.activeFile.path === file.path &&
state.viewer === viewerTypes.diff
) {
return false;
}
commit(types.ADD_PENDING_TAB, { file, keyPrefix }); commit(types.ADD_PENDING_TAB, { file, keyPrefix });
......
...@@ -56,10 +56,16 @@ export const allBlobs = state => ...@@ -56,10 +56,16 @@ export const allBlobs = state =>
export const getChangedFile = state => path => state.changedFiles.find(f => f.path === path); export const getChangedFile = state => path => state.changedFiles.find(f => f.path === path);
export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path); export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path);
export const lastOpenedFile = state =>
[...state.changedFiles, ...state.stagedFiles].sort((a, b) => b.lastOpenedAt - a.lastOpenedAt)[0];
export const isEditModeActive = state => state.currentActivityView === activityBarViews.edit; export const isEditModeActive = state => state.currentActivityView === activityBarViews.edit;
export const isCommitModeActive = state => state.currentActivityView === activityBarViews.commit; export const isCommitModeActive = state => state.currentActivityView === activityBarViews.commit;
export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review; export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review;
export const someUncommitedChanges = state =>
!!(state.changedFiles.length || state.stagedFiles.length);
export const getChangesInFolder = state => path => { export const getChangesInFolder = state => path => {
const changedFilesCount = state.changedFiles.filter(f => filePathMatches(f, path)).length; const changedFilesCount = state.changedFiles.filter(f => filePathMatches(f, path)).length;
const stagedFilesCount = state.stagedFiles.filter( const stagedFilesCount = state.stagedFiles.filter(
......
...@@ -8,6 +8,7 @@ import router from '../../../ide_router'; ...@@ -8,6 +8,7 @@ import router from '../../../ide_router';
import service from '../../../services'; 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 { activityBarViews } from '../../../constants';
import eventHub from '../../../eventhub'; import eventHub from '../../../eventhub';
export const updateCommitMessage = ({ commit }, message) => { export const updateCommitMessage = ({ commit }, message) => {
...@@ -75,7 +76,7 @@ export const checkCommitStatus = ({ rootState }) => ...@@ -75,7 +76,7 @@ export const checkCommitStatus = ({ rootState }) =>
export const updateFilesAfterCommit = ( export const updateFilesAfterCommit = (
{ commit, dispatch, state, rootState, rootGetters }, { commit, dispatch, state, rootState, rootGetters },
{ data, branch }, { data },
) => { ) => {
const selectedProject = rootState.projects[rootState.currentProjectId]; const selectedProject = rootState.projects[rootState.currentProjectId];
const lastCommit = { const lastCommit = {
...@@ -126,15 +127,9 @@ export const updateFilesAfterCommit = ( ...@@ -126,15 +127,9 @@ export const updateFilesAfterCommit = (
changed: !!changedFile, changed: !!changedFile,
}); });
}); });
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH && rootGetters.activeFile) {
router.push(
`/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`,
);
}
}; };
export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => { export const commitChanges = ({ commit, state, getters, dispatch, rootState, rootGetters }) => {
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(getters.branchName, newBranch, state, rootState);
const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus'); const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus');
...@@ -187,6 +182,34 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) = ...@@ -187,6 +182,34 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) =
commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true }); commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true });
}, 5000); }, 5000);
}) })
.then(() => {
if (rootGetters.lastOpenedFile) {
dispatch(
'openPendingTab',
{
file: rootGetters.lastOpenedFile,
},
{ root: true },
)
.then(changeViewer => {
if (changeViewer) {
dispatch('updateViewer', 'diff', { root: true });
}
})
.catch(e => {
throw e;
});
} else {
dispatch('updateActivityBarView', activityBarViews.edit, { root: true });
dispatch('updateViewer', 'editor', { root: true });
router.push(
`/project/${rootState.currentProjectId}/blob/${getters.branchName}/${
rootGetters.activeFile.path
}`,
);
}
})
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH)); .then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH));
}) })
.catch(err => { .catch(err => {
......
/* eslint-disable no-param-reassign */
import * as types from '../mutation_types'; import * as types from '../mutation_types';
export default { export default {
...@@ -169,36 +170,24 @@ export default { ...@@ -169,36 +170,24 @@ export default {
}); });
}, },
[types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) { [types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) {
const key = `${keyPrefix}-${file.key}`; state.entries[file.path].opened = false;
const pendingTab = state.openFiles.find(f => f.key === key && f.pending); state.entries[file.path].active = false;
let openFiles = state.openFiles.map(f => Object.assign(f, { active: false, opened: false })); state.entries[file.path].lastOpenedAt = new Date().getTime();
state.openFiles.forEach(f =>
if (!pendingTab) { Object.assign(f, {
const openFile = openFiles.find(f => f.path === file.path); opened: false,
active: false,
openFiles = openFiles.concat(openFile ? null : file).reduce((acc, f) => { }),
if (!f) return acc; );
state.openFiles = [
if (f.path === file.path) { {
return acc.concat({ ...file,
...f, key: `${keyPrefix}-${file.key}`,
content: file.content, pending: true,
active: true, opened: true,
pending: true, active: true,
opened: true, },
key, ];
});
}
return acc.concat(f);
}, []);
} else {
openFiles = state.openFiles.map(f =>
Object.assign(f, { active: f.key === key, opened: f.key === key }),
);
}
Object.assign(state, { openFiles });
}, },
[types.REMOVE_PENDING_TAB](state, file) { [types.REMOVE_PENDING_TAB](state, file) {
Object.assign(state, { Object.assign(state, {
......
...@@ -192,11 +192,11 @@ ...@@ -192,11 +192,11 @@
right: 3px; right: 3px;
} }
&:hover { &:not([disabled]):hover {
background-color: $theme-gray-200; background-color: $theme-gray-200;
} }
&:focus { &:not([disabled]):focus {
background-color: $blue-500; background-color: $blue-500;
color: $white-light; color: $white-light;
outline: 0; outline: 0;
...@@ -443,6 +443,7 @@ ...@@ -443,6 +443,7 @@
} }
.multi-file-commit-panel-inner { .multi-file-commit-panel-inner {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
...@@ -484,14 +485,13 @@ ...@@ -484,14 +485,13 @@
align-items: center; align-items: center;
margin-bottom: 0; margin-bottom: 0;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
padding: $gl-btn-padding 0; padding: $gl-btn-padding $gl-padding;
min-height: 56px;
} }
.multi-file-commit-panel-header-title { .multi-file-commit-panel-header-title {
display: flex; display: flex;
flex: 1; flex: 1;
padding-left: $grid-size; align-items: center;
svg { svg {
margin-right: $gl-btn-padding; margin-right: $gl-btn-padding;
...@@ -507,7 +507,7 @@ ...@@ -507,7 +507,7 @@
.multi-file-commit-list { .multi-file-commit-list {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: $gl-padding 0; padding: $gl-padding;
min-height: 60px; min-height: 60px;
} }
...@@ -602,30 +602,24 @@ ...@@ -602,30 +602,24 @@
} }
.multi-file-commit-form { .multi-file-commit-form {
position: relative;
padding: $gl-padding; padding: $gl-padding;
background-color: $white-light;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
border-left: 1px solid $white-dark;
transition: all 0.3s ease;
.btn { .btn {
font-size: $gl-font-size; font-size: $gl-font-size;
} }
.multi-file-commit-panel-success-message {
top: 0;
}
} }
.multi-file-commit-panel-bottom { .multi-file-commit-panel-bottom {
position: relative; position: relative;
.multi-file-commit-panel-success-message {
position: absolute;
top: 1px;
left: 3px;
bottom: 0;
right: 0;
z-index: 10;
background: $gray-light;
overflow: auto;
display: flex;
flex-direction: column;
justify-content: center;
}
} }
.dirty-diff { .dirty-diff {
...@@ -779,17 +773,36 @@ ...@@ -779,17 +773,36 @@
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
min-height: 140px; min-height: 140px;
padding: 0 16px;
&.is-first {
border-bottom: 1px solid $white-dark;
}
} }
.ide-staged-action-btn { .ide-staged-action-btn {
margin-left: auto; margin-left: auto;
color: $gl-link-color; line-height: 22px;
}
.ide-commit-file-count {
min-width: 22px;
margin-left: auto;
background-color: $gray-light;
border-radius: $border-radius-default;
border: 1px solid $white-dark;
line-height: 20px;
text-align: center;
} }
.ide-commit-radios { .ide-commit-radios {
label { label {
font-weight: normal; font-weight: normal;
&.is-disabled {
.ide-radio-label {
text-decoration: line-through;
}
}
} }
.help-block { .help-block {
...@@ -853,6 +866,7 @@ ...@@ -853,6 +866,7 @@
.ide-activity-bar { .ide-activity-bar {
position: relative; position: relative;
flex: 0 0 60px; flex: 0 0 60px;
z-index: 1;
} }
.ide-file-finder-overlay { .ide-file-finder-overlay {
...@@ -969,6 +983,40 @@ ...@@ -969,6 +983,40 @@
} }
} }
.commit-form-compact {
.btn {
margin-bottom: 8px;
}
p {
margin-bottom: 0;
}
}
.commit-form-slide-up-enter-active,
.commit-form-slide-up-leave-active {
position: absolute;
top: 16px;
left: 16px;
right: 16px;
transition: all 0.3s ease;
}
.is-full .commit-form-slide-up-enter,
.is-compact .commit-form-slide-up-leave-to {
transform: translateY(100%);
}
.is-full .commit-form-slide-up-enter-to,
.is-compact .commit-form-slide-up-leave {
transform: translateY(0);
}
.commit-form-slide-up-enter,
.commit-form-slide-up-leave-to {
opacity: 0;
}
.ide-review-header { .ide-review-header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
...@@ -996,6 +1044,20 @@ ...@@ -996,6 +1044,20 @@
line-height: 34px; line-height: 34px;
} }
.multi-file-commit-panel-success-message {
position: absolute;
top: 61px;
left: 1px;
bottom: 0;
right: 0;
z-index: 10;
background: $white-light;
overflow: auto;
display: flex;
flex-direction: column;
justify-content: center;
}
.ide-review-button-holder { .ide-review-button-holder {
display: flex; display: flex;
width: 100%; width: 100%;
......
...@@ -46,7 +46,8 @@ feature 'Multi-file editor new directory', :js do ...@@ -46,7 +46,8 @@ feature 'Multi-file editor new directory', :js do
find('.js-ide-commit-mode').click find('.js-ide-commit-mode').click
click_button 'Stage all' find('.multi-file-commit-list-item').hover
first('.multi-file-discard-btn .btn').click
fill_in('commit-message', with: 'commit message ide') fill_in('commit-message', with: 'commit message ide')
......
...@@ -36,7 +36,8 @@ feature 'Multi-file editor new file', :js do ...@@ -36,7 +36,8 @@ feature 'Multi-file editor new file', :js do
find('.js-ide-commit-mode').click find('.js-ide-commit-mode').click
click_button 'Stage all' find('.multi-file-commit-list-item').hover
first('.multi-file-discard-btn .btn').click
fill_in('commit-message', with: 'commit message ide') fill_in('commit-message', with: 'commit message ide')
......
import Vue from 'vue';
import store from '~/ide/stores';
import CommitForm from '~/ide/components/commit_sidebar/form.vue';
import { activityBarViews } from '~/ide/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
import { resetStore } from '../../helpers';
describe('IDE commit form', () => {
const Component = Vue.extend(CommitForm);
let vm;
beforeEach(() => {
spyOnProperty(window, 'innerHeight').and.returnValue(800);
store.state.changedFiles.push('test');
vm = createComponentWithStore(Component, store).$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('enables button when has changes', () => {
expect(vm.$el.querySelector('[disabled]')).toBe(null);
});
describe('compact', () => {
it('renders commit button in compact mode', () => {
expect(vm.$el.querySelector('.btn-primary')).not.toBeNull();
expect(vm.$el.querySelector('.btn-primary').textContent).toContain('Commit');
});
it('does not render form', () => {
expect(vm.$el.querySelector('form')).toBeNull();
});
it('renders overview text', done => {
vm.$store.state.stagedFiles.push('test');
vm.$nextTick(() => {
expect(vm.$el.querySelector('p').textContent).toContain('1 unstaged and 1 staged changes');
done();
});
});
it('shows form when clicking commit button', done => {
vm.$el.querySelector('.btn-primary').click();
vm.$nextTick(() => {
expect(vm.$el.querySelector('form')).not.toBeNull();
done();
});
});
it('toggles activity bar vie when clicking commit button', done => {
vm.$el.querySelector('.btn-primary').click();
vm.$nextTick(() => {
expect(store.state.currentActivityView).toBe(activityBarViews.commit);
done();
});
});
});
describe('full', () => {
beforeEach(done => {
vm.isCompact = false;
vm.$nextTick(done);
});
it('updates commitMessage in store on input', done => {
const textarea = vm.$el.querySelector('textarea');
textarea.value = 'testing commit message';
textarea.dispatchEvent(new Event('input'));
getSetTimeoutPromise()
.then(() => {
expect(vm.$store.state.commit.commitMessage).toBe('testing commit message');
})
.then(done)
.catch(done.fail);
});
it('updating currentActivityView not to commit view sets compact mode', done => {
store.state.currentActivityView = 'a';
vm.$nextTick(() => {
expect(vm.isCompact).toBe(true);
done();
});
});
describe('discard draft button', () => {
it('hidden when commitMessage is empty', () => {
expect(vm.$el.querySelector('.btn-default').textContent).toContain('Collapse');
});
it('resets commitMessage when clicking discard button', done => {
vm.$store.state.commit.commitMessage = 'testing commit message';
getSetTimeoutPromise()
.then(() => {
vm.$el.querySelector('.btn-default').click();
})
.then(Vue.nextTick)
.then(() => {
expect(vm.$store.state.commit.commitMessage).not.toBe('testing commit message');
})
.then(done)
.catch(done.fail);
});
});
describe('when submitting', () => {
beforeEach(() => {
spyOn(vm, 'commitChanges');
vm.$store.state.stagedFiles.push('test');
});
it('calls commitChanges', done => {
vm.$store.state.commit.commitMessage = 'testing commit message';
getSetTimeoutPromise()
.then(() => {
vm.$el.querySelector('.btn-success').click();
})
.then(Vue.nextTick)
.then(() => {
expect(vm.commitChanges).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
});
});
});
...@@ -49,16 +49,4 @@ describe('Multi-file editor commit sidebar list', () => { ...@@ -49,16 +49,4 @@ describe('Multi-file editor commit sidebar list', () => {
expect(vm.$el.textContent).toContain('No changes'); expect(vm.$el.textContent).toContain('No changes');
}); });
}); });
describe('action button', () => {
beforeEach(() => {
spyOn(vm, 'stageAllChanges');
});
it('calls store action when clicked', () => {
vm.$el.querySelector('.ide-staged-action-btn').click();
expect(vm.stageAllChanges).toHaveBeenCalled();
});
});
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/ide/stores'; import store from '~/ide/stores';
import service from '~/ide/services'; import service from '~/ide/services';
import router from '~/ide/ide_router';
import repoCommitSection from '~/ide/components/repo_commit_section.vue'; import repoCommitSection from '~/ide/components/repo_commit_section.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers'; import { file, resetStore } from '../helpers';
describe('RepoCommitSection', () => { describe('RepoCommitSection', () => {
...@@ -60,6 +60,8 @@ describe('RepoCommitSection', () => { ...@@ -60,6 +60,8 @@ describe('RepoCommitSection', () => {
} }
beforeEach(done => { beforeEach(done => {
spyOn(router, 'push');
vm = createComponent(); vm = createComponent();
spyOn(service, 'getTreeData').and.returnValue( spyOn(service, 'getTreeData').and.returnValue(
...@@ -105,30 +107,28 @@ describe('RepoCommitSection', () => { ...@@ -105,30 +107,28 @@ describe('RepoCommitSection', () => {
it('renders a commit section', () => { it('renders a commit section', () => {
const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')]; const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
const submitCommit = vm.$el.querySelector('form .btn');
const allFiles = vm.$store.state.changedFiles.concat(vm.$store.state.stagedFiles); const allFiles = vm.$store.state.changedFiles.concat(vm.$store.state.stagedFiles);
expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
expect(changedFileElements.length).toEqual(4); expect(changedFileElements.length).toEqual(4);
changedFileElements.forEach((changedFile, i) => { changedFileElements.forEach((changedFile, i) => {
expect(changedFile.textContent.trim()).toContain(allFiles[i].path); expect(changedFile.textContent.trim()).toContain(allFiles[i].path);
}); });
expect(submitCommit.disabled).toBeTruthy();
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
}); });
it('adds changed files into staged files', done => { it('adds changed files into staged files', done => {
vm.$el.querySelector('.ide-staged-action-btn').click(); vm.$el.querySelector('.multi-file-discard-btn .btn').click();
vm
Vue.nextTick(() => { .$nextTick()
expect(vm.$el.querySelector('.ide-commit-list-container').textContent).toContain( .then(() => vm.$el.querySelector('.multi-file-discard-btn .btn').click())
'No changes', .then(vm.$nextTick)
); .then(() => {
expect(vm.$el.querySelector('.ide-commit-list-container').textContent).toContain(
done(); 'No changes',
}); );
})
.then(done)
.catch(done.fail);
}); });
it('stages a single file', done => { it('stages a single file', done => {
...@@ -156,18 +156,6 @@ describe('RepoCommitSection', () => { ...@@ -156,18 +156,6 @@ describe('RepoCommitSection', () => {
}); });
}); });
it('removes all staged files', done => {
vm.$el.querySelectorAll('.ide-staged-action-btn')[1].click();
Vue.nextTick(() => {
expect(vm.$el.querySelectorAll('.ide-commit-list-container')[1].textContent).toContain(
'No changes',
);
done();
});
});
it('unstages a single file', done => { it('unstages a single file', done => {
vm.$el vm.$el
.querySelectorAll('.multi-file-discard-btn')[2] .querySelectorAll('.multi-file-discard-btn')[2]
...@@ -183,60 +171,10 @@ describe('RepoCommitSection', () => { ...@@ -183,60 +171,10 @@ describe('RepoCommitSection', () => {
}); });
}); });
it('updates commitMessage in store on input', done => { describe('mounted', () => {
const textarea = vm.$el.querySelector('textarea'); it('opens last opened file', () => {
expect(store.state.openFiles.length).toBe(1);
textarea.value = 'testing commit message'; expect(store.state.openFiles[0].pending).toBe(true);
textarea.dispatchEvent(new Event('input'));
getSetTimeoutPromise()
.then(() => {
expect(vm.$store.state.commit.commitMessage).toBe('testing commit message');
})
.then(done)
.catch(done.fail);
});
describe('discard draft button', () => {
it('hidden when commitMessage is empty', () => {
expect(vm.$el.querySelector('.multi-file-commit-form .btn-default')).toBeNull();
});
it('resets commitMessage when clicking discard button', done => {
vm.$store.state.commit.commitMessage = 'testing commit message';
getSetTimeoutPromise()
.then(() => {
vm.$el.querySelector('.multi-file-commit-form .btn-default').click();
})
.then(Vue.nextTick)
.then(() => {
expect(vm.$store.state.commit.commitMessage).not.toBe('testing commit message');
})
.then(done)
.catch(done.fail);
});
});
describe('when submitting', () => {
beforeEach(() => {
spyOn(vm, 'commitChanges');
});
it('calls commitChanges', done => {
vm.$store.state.commit.commitMessage = 'testing commit message';
getSetTimeoutPromise()
.then(() => {
vm.$el.querySelector('.multi-file-commit-form .btn-success').click();
})
.then(Vue.nextTick)
.then(() => {
expect(vm.commitChanges).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
}); });
}); });
}); });
...@@ -589,20 +589,6 @@ describe('IDE store file actions', () => { ...@@ -589,20 +589,6 @@ describe('IDE store file actions', () => {
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
it('returns false when passed in file is active & viewer is diff', done => {
f.active = true;
store.state.openFiles.push(f);
store.state.viewer = 'diff';
store
.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
.then(added => {
expect(added).toBe(false);
})
.then(done)
.catch(done.fail);
});
}); });
describe('removePendingTab', () => { describe('removePendingTab', () => {
......
...@@ -289,21 +289,6 @@ describe('IDE commit module actions', () => { ...@@ -289,21 +289,6 @@ describe('IDE commit module actions', () => {
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
it('pushes route to new branch if commitAction is new branch', done => {
store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
store
.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(router.push).toHaveBeenCalledWith(`/project/abcproject/blob/master/${f.path}`);
})
.then(done)
.catch(done.fail);
});
}); });
describe('commitChanges', () => { describe('commitChanges', () => {
...@@ -391,21 +376,6 @@ describe('IDE commit module actions', () => { ...@@ -391,21 +376,6 @@ describe('IDE commit module actions', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('pushes router to new route', done => {
store
.dispatch('commit/commitChanges')
.then(() => {
expect(router.push).toHaveBeenCalledWith(
`/project/${store.state.currentProjectId}/blob/${
store.getters['commit/newBranchName']
}/changed`,
);
done();
})
.catch(done.fail);
});
it('sets last Commit Msg', done => { it('sets last Commit Msg', done => {
store store
.dispatch('commit/commitChanges') .dispatch('commit/commitChanges')
......
...@@ -267,41 +267,23 @@ describe('IDE store file mutations', () => { ...@@ -267,41 +267,23 @@ describe('IDE store file mutations', () => {
it('adds file into openFiles as pending', () => { it('adds file into openFiles as pending', () => {
mutations.ADD_PENDING_TAB(localState, { file: localFile }); mutations.ADD_PENDING_TAB(localState, { file: localFile });
expect(localState.openFiles.length).toBe(2);
expect(localState.openFiles[1].pending).toBe(true);
expect(localState.openFiles[1].key).toBe(`pending-${localFile.key}`);
});
it('updates open file to pending', () => {
mutations.ADD_PENDING_TAB(localState, { file: localState.openFiles[0] });
expect(localState.openFiles.length).toBe(1); expect(localState.openFiles.length).toBe(1);
expect(localState.openFiles[0].pending).toBe(true);
expect(localState.openFiles[0].key).toBe(`pending-${localFile.key}`);
}); });
it('updates pending open file to active', () => { it('only allows 1 open pending file', () => {
localState.openFiles.push({ const newFile = file('test');
...localFile, localState.entries[newFile.path] = newFile;
pending: true,
});
mutations.ADD_PENDING_TAB(localState, { file: localFile }); mutations.ADD_PENDING_TAB(localState, { file: localFile });
expect(localState.openFiles[1].pending).toBe(true); expect(localState.openFiles.length).toBe(1);
expect(localState.openFiles[1].active).toBe(true);
});
it('sets all openFiles to not active', () => {
mutations.ADD_PENDING_TAB(localState, { file: localFile });
expect(localState.openFiles.length).toBe(2); mutations.ADD_PENDING_TAB(localState, { file: file('test') });
localState.openFiles.forEach(f => { expect(localState.openFiles.length).toBe(1);
if (f.pending) { expect(localState.openFiles[0].name).toBe('test');
expect(f.active).toBe(true);
} else {
expect(f.active).toBe(false);
}
});
}); });
}); });
......
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