GitLab wurde aktualisiert. Dank regelmäßiger Updates bleibt das THM GitLab sicher und Sie profitieren von den neuesten Funktionen. Vielen Dank für Ihre Geduld.

Commit b7f3d747 authored by André Luís's avatar André Luís Committed by Phil Hughes
Browse files

Resolve "Clean up bottom status bar Web IDE"

parent 68b71df6
...@@ -65,61 +65,63 @@ export default { ...@@ -65,61 +65,63 @@ export default {
</script> </script>
<template> <template>
<div <article class="ide">
class="ide-view"
>
<find-file
v-show="fileFindVisible"
/>
<ide-sidebar />
<div <div
class="multi-file-edit-pane" class="ide-view"
> >
<template <find-file
v-if="activeFile" v-show="fileFindVisible"
> />
<repo-tabs <ide-sidebar />
:active-file="activeFile" <div
:files="openFiles" class="multi-file-edit-pane"
:viewer="viewer"
:has-changes="hasChanges"
:merge-request-id="currentMergeRequestId"
/>
<repo-editor
class="multi-file-edit-pane-content"
:file="activeFile"
/>
<ide-status-bar
:file="activeFile"
/>
</template>
<template
v-else
> >
<div <template
v-once v-if="activeFile"
class="ide-empty-state" >
<repo-tabs
:active-file="activeFile"
:files="openFiles"
:viewer="viewer"
:has-changes="hasChanges"
:merge-request-id="currentMergeRequestId"
/>
<repo-editor
class="multi-file-edit-pane-content"
:file="activeFile"
/>
</template>
<template
v-else
> >
<div class="row js-empty-state"> <div
<div class="col-xs-12"> v-once
<div class="svg-content svg-250"> class="ide-empty-state"
<img :src="emptyStateSvgPath" /> >
<div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
<img :src="emptyStateSvgPath" />
</div>
</div> </div>
</div> <div class="col-xs-12">
<div class="col-xs-12"> <div class="text-content text-center">
<div class="text-content text-center"> <h4>
<h4> Welcome to the GitLab IDE
Welcome to the GitLab IDE </h4>
</h4> <p>
<p> You can select a file in the left sidebar to begin
You can select a file in the left sidebar to begin editing and use the right sidebar to commit your changes.
editing and use the right sidebar to commit your changes. </p>
</p> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </template>
</template> </div>
</div> </div>
</div> <ide-status-bar
:file="activeFile"
/>
</article>
</template> </template>
<script> <script>
import { mapGetters } 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';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
export default { export default {
components: { components: {
icon, icon,
userAvatarImage,
}, },
directives: { directives: {
tooltip, tooltip,
...@@ -14,40 +17,93 @@ export default { ...@@ -14,40 +17,93 @@ export default {
props: { props: {
file: { file: {
type: Object, type: Object,
required: true, required: false,
default: null,
},
},
data() {
return {
lastCommitFormatedAge: null,
};
},
computed: {
...mapGetters(['currentProject', 'lastCommit']),
},
mounted() {
this.startTimer();
},
beforeDestroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
},
methods: {
startTimer() {
this.intervalId = setInterval(() => {
this.commitAgeUpdate();
}, 1000);
},
commitAgeUpdate() {
if (this.lastCommit) {
this.lastCommitFormatedAge = this.timeFormated(this.lastCommit.committed_date);
}
},
getCommitPath(shortSha) {
return `${this.currentProject.web_url}/commit/${shortSha}`;
}, },
}, },
}; };
</script> </script>
<template> <template>
<div class="ide-status-bar"> <footer class="ide-status-bar">
<div> <div
<div v-if="file.lastCommit && file.lastCommit.id"> class="ide-status-branch"
Last commit: v-if="lastCommit && lastCommitFormatedAge"
<a >
v-tooltip <icon
:title="file.lastCommit.message" name="commit"
:href="file.lastCommit.url" />
> <a
{{ timeFormated(file.lastCommit.updatedAt) }} by v-tooltip
{{ file.lastCommit.author }} class="commit-sha"
</a> :title="lastCommit.message"
</div> :href="getCommitPath(lastCommit.short_id)"
>{{ lastCommit.short_id }}</a>
by
{{ lastCommit.author_name }}
<time
v-tooltip
data-placement="top"
data-container="body"
:datetime="lastCommit.committed_date"
:title="tooltipTitle(lastCommit.committed_date)"
>
{{ lastCommitFormatedAge }}
</time>
</div> </div>
<div class="text-right"> <div
v-if="file"
class="ide-status-file"
>
{{ file.name }} {{ file.name }}
</div> </div>
<div class="text-right"> <div
v-if="file"
class="ide-status-file"
>
{{ file.eol }} {{ file.eol }}
</div> </div>
<div <div
class="text-right" class="ide-status-file"
v-if="!file.binary"> v-if="file && !file.binary">
{{ file.editorRow }}:{{ file.editorColumn }} {{ file.editorRow }}:{{ file.editorColumn }}
</div> </div>
<div class="text-right"> <div
v-if="file"
class="ide-status-file"
>
{{ file.fileLanguage }} {{ file.fileLanguage }}
</div> </div>
</div> </footer>
</template> </template>
...@@ -72,3 +72,26 @@ export const getBranchData = ( ...@@ -72,3 +72,26 @@ export const getBranchData = (
resolve(state.projects[`${projectId}`].branches[branchId]); resolve(state.projects[`${projectId}`].branches[branchId]);
} }
}); });
export const refreshLastCommitData = (
{ commit, state, dispatch },
{ projectId, branchId } = {},
) => service
.getBranchData(projectId, branchId)
.then(({ data }) => {
commit(types.SET_BRANCH_COMMIT, {
projectId,
branchId,
commit: data.commit,
});
})
.catch(() => {
flash(
'Error loading last commit.',
'alert',
document,
null,
false,
true,
);
});
...@@ -81,5 +81,11 @@ export const getUnstagedFilesCountForPath = state => path => ...@@ -81,5 +81,11 @@ export const getUnstagedFilesCountForPath = state => path =>
export const getStagedFilesCountForPath = state => path => export const getStagedFilesCountForPath = state => path =>
getChangesCountForFiles(state.stagedFiles, path); getChangesCountForFiles(state.stagedFiles, path);
export const lastCommit = (state, getters) => {
const branch = getters.currentProject && getters.currentProject.branches[state.currentBranchId];
return branch ? branch.commit : null;
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -210,7 +210,11 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo ...@@ -210,7 +210,11 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
); );
} }
}) })
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH)); .then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH))
.then(() => dispatch('refreshLastCommitData', {
projectId: rootState.currentProjectId,
branchId: rootState.currentBranchId,
}, { root: true }));
}) })
.catch(err => { .catch(err => {
let errMsg = __('Error committing changes. Please try again.'); let errMsg = __('Error committing changes. Please try again.');
......
...@@ -20,6 +20,7 @@ export const SET_MERGE_REQUEST_VERSIONS = 'SET_MERGE_REQUEST_VERSIONS'; ...@@ -20,6 +20,7 @@ export const SET_MERGE_REQUEST_VERSIONS = 'SET_MERGE_REQUEST_VERSIONS';
// Branch Mutation Types // Branch Mutation Types
export const SET_BRANCH = 'SET_BRANCH'; export const SET_BRANCH = 'SET_BRANCH';
export const SET_BRANCH_COMMIT = 'SET_BRANCH_COMMIT';
export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE'; export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN'; export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
......
...@@ -23,4 +23,9 @@ export default { ...@@ -23,4 +23,9 @@ export default {
workingReference: reference, workingReference: reference,
}); });
}, },
[types.SET_BRANCH_COMMIT](state, { projectId, branchId, commit }) {
Object.assign(state.projects[projectId].branches[branchId], {
commit,
});
},
}; };
...@@ -230,6 +230,7 @@ $row-hover: $blue-50; ...@@ -230,6 +230,7 @@ $row-hover: $blue-50;
$row-hover-border: $blue-200; $row-hover-border: $blue-200;
$progress-color: #c0392b; $progress-color: #c0392b;
$header-height: 40px; $header-height: 40px;
$ide-statusbar-height: 27px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$limited-layout-width: 990px; $limited-layout-width: 990px;
$limited-layout-width-sm: 790px; $limited-layout-width-sm: 790px;
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
margin-top: 0; margin-top: 0;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
padding-bottom: $ide-statusbar-height;
&.is-collapsed { &.is-collapsed {
.ide-file-list { .ide-file-list {
...@@ -375,7 +376,13 @@ ...@@ -375,7 +376,13 @@
padding: $gl-bar-padding $gl-padding; padding: $gl-bar-padding $gl-padding;
background: $white-light; background: $white-light;
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
height: $ide-statusbar-height;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
> div + div { > div + div {
padding-left: $gl-padding; padding-left: $gl-padding;
...@@ -386,6 +393,14 @@ ...@@ -386,6 +393,14 @@
} }
} }
.ide-status-file {
text-align: right;
.ide-status-branch + &,
&:first-child {
margin-left: auto;
}
}
// Not great, but this is to deal with our current output // Not great, but this is to deal with our current output
.multi-file-preview-holder { .multi-file-preview-holder {
height: 100%; height: 100%;
......
---
title: Clean up WebIDE status bar and add useful info
merge_request:
author:
type: changed
import Vue from 'vue';
import store from '~/ide/stores';
import ideStatusBar from '~/ide/components/ide_status_bar.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
import { projectData } from '../mock_data';
describe('ideStatusBar', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(ideStatusBar);
store.state.currentProjectId = 'abcproject';
store.state.projects.abcproject = projectData;
vm = createComponentWithStore(Component, store).$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('renders the statusbar', () => {
expect(vm.$el.className).toBe('ide-status-bar');
});
describe('mounted', () => {
it('triggers a setInterval', () => {
expect(vm.intervalId).not.toBe(null);
});
});
describe('commitAgeUpdate', () => {
beforeEach(function() {
jasmine.clock().install();
spyOn(vm, 'commitAgeUpdate').and.callFake(() => {});
vm.startTimer();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it('gets called every second', () => {
expect(vm.commitAgeUpdate).not.toHaveBeenCalled();
jasmine.clock().tick(1100);
expect(vm.commitAgeUpdate.calls.count()).toEqual(1);
jasmine.clock().tick(1000);
expect(vm.commitAgeUpdate.calls.count()).toEqual(2);
});
});
describe('getCommitPath', () => {
it('returns the path to the commit details', () => {
expect(vm.getCommitPath('abc123de')).toBe('/commit/abc123de');
});
});
});
import {
refreshLastCommitData,
} from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
import { resetStore } from '../../helpers';
import testAction from '../../../helpers/vuex_action_helper';
describe('IDE store project actions', () => {
beforeEach(() => {
store.state.projects.abcproject = {};
});
afterEach(() => {
resetStore(store);
});
describe('refreshLastCommitData', () => {
beforeEach(() => {
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
branches: {
master: {
commit: null,
},
},
};
});
it('calls the service', done => {
spyOn(service, 'getBranchData').and.returnValue(
Promise.resolve({
data: {
commit: { id: '123' },
},
}),
);
store
.dispatch('refreshLastCommitData', {
projectId: store.state.currentProjectId,
branchId: store.state.currentBranchId,
})
.then(() => {
expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
done();
})
.catch(done.fail);
});
it('commits getBranchData', done => {
testAction(
refreshLastCommitData,
{},
{},
[{
type: 'SET_BRANCH_COMMIT',
payload: {
projectId: 'abcproject',
branchId: 'master',
commit: { id: '123' },
},
}], // mutations
[], // action
done,
);
});
});
});
...@@ -141,4 +141,24 @@ describe('IDE store getters', () => { ...@@ -141,4 +141,24 @@ describe('IDE store getters', () => {
expect(getters.getChangesInFolder(localState)('test')).toBe(2); expect(getters.getChangesInFolder(localState)('test')).toBe(2);
}); });
}); });
describe('lastCommit', () => {
it('returns the last commit of the current branch on the current project', () => {
const commitTitle = 'Example commit title';
const localGetters = {
currentProject: {
branches: {
'example-branch': {
commit: {
title: commitTitle,
},
},
},
},
};
localState.currentBranchId = 'example-branch';
expect(getters.lastCommit(localState, localGetters).title).toBe(commitTitle);
});
});
}); });
...@@ -15,4 +15,26 @@ describe('Multi-file store branch mutations', () => { ...@@ -15,4 +15,26 @@ describe('Multi-file store branch mutations', () => {
expect(localState.currentBranchId).toBe('master'); expect(localState.currentBranchId).toBe('master');
}); });