Commit a76cd883 authored by Phil Hughes's avatar Phil Hughes

Added IDE commit panel

Closes #40041
parent 882dac68
<script>
import icon from '../../../vue_shared/components/icon.vue';
import listItem from './list_item.vue';
import listCollapsed from './list_collapsed.vue';
export default {
components: {
icon,
listItem,
listCollapsed,
},
props: {
title: {
type: String,
required: true,
},
fileList: {
type: Array,
required: true,
},
collapsed: {
type: Boolean,
required: true,
},
},
methods: {
toggleCollapsed() {
this.$emit('toggleCollapsed');
},
},
};
</script>
<template>
<div class="multi-file-commit-panel-section">
<header
class="multi-file-commit-panel-header"
:class="{
'is-collapsed': collapsed,
}"
>
<icon
name="list-bulleted"
:size="18"
css-classes="append-right-default"
/>
<template v-if="!collapsed">
{{ title }}
<button
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
@click="toggleCollapsed"
>
<i
aria-hidden="true"
class="fa fa-angle-double-right"
>
</i>
</button>
</template>
</header>
<div class="multi-file-commit-list">
<list-collapsed
v-if="collapsed"
:file-list="fileList"
/>
<template v-else>
<ul
v-if="fileList.length"
class="list-unstyled append-bottom-0"
>
<li
v-for="file in fileList"
:key="file.key"
>
<list-item
:file="file"
/>
</li>
</ul>
<div
v-else
class="help-block prepend-top-0"
>
No changes
</div>
</template>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
fileList: {
type: Array,
required: true,
},
},
computed: {
...mapGetters([
'addedFiles',
'modifiedFiles',
]),
},
};
</script>
<template>
<div
class="multi-file-commit-list-collapsed text-center"
>
<icon
name="file-addition"
:size="18"
css-classes="multi-file-addition append-bottom-10"
/>
{{ addedFiles.length }}
<icon
name="file-modified"
:size="18"
css-classes="multi-file-modified prepend-top-10 append-bottom-10"
/>
{{ modifiedFiles.length }}
</div>
</template>
<script>
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
file: {
type: Object,
required: true,
},
},
computed: {
iconName() {
return this.file.tempFile ? 'file-addition' : 'file-modifed';
},
iconClass() {
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
},
},
};
</script>
<template>
<div class="multi-file-commit-list-item">
<icon
:name="iconName"
:size="16"
:css-classes="iconClass"
/>
<span class="multi-file-commit-list-path">
{{ file.path }}
</span>
</div>
</template>
...@@ -40,20 +40,24 @@ export default { ...@@ -40,20 +40,24 @@ export default {
</script> </script>
<template> <template>
<div class="repository-view"> <div
<div class="tree-content-holder" :class="{'tree-content-holder-mini' : isCollapsed}"> class="multi-file"
<repo-sidebar/> :class="{
<div 'is-collapsed': isCollapsed
v-if="isCollapsed" }"
class="panel-right" >
> <repo-sidebar/>
<repo-tabs/> <div
<component v-if="isCollapsed"
:is="currentBlobView" class="multi-file-edit-pane"
/> >
<repo-file-buttons/> <repo-tabs />
</div> <component
class="multi-file-edit-pane-content"
:is="currentBlobView"
/>
<repo-file-buttons />
</div> </div>
<repo-commit-section v-if="changedFiles.length" /> <repo-commit-section />
</div> </div>
</template> </template>
<script> <script>
import { mapGetters, mapState, mapActions } from 'vuex'; import { mapGetters, mapState, mapActions } from 'vuex';
import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue'; import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import { n__ } from '../../locale'; import commitFilesList from './commit_sidebar/list.vue';
export default { export default {
components: { components: {
PopupDialog, PopupDialog,
icon,
commitFilesList,
},
directives: {
tooltip,
}, },
data() { data() {
return { return {
...@@ -13,6 +20,7 @@ export default { ...@@ -13,6 +20,7 @@ export default {
submitCommitsLoading: false, submitCommitsLoading: false,
startNewMR: false, startNewMR: false,
commitMessage: '', commitMessage: '',
collapsed: true,
}; };
}, },
computed: { computed: {
...@@ -23,10 +31,10 @@ export default { ...@@ -23,10 +31,10 @@ export default {
'changedFiles', 'changedFiles',
]), ]),
commitButtonDisabled() { commitButtonDisabled() {
return !this.commitMessage || this.submitCommitsLoading; return !this.commitMessage || this.submitCommitsLoading || !this.changedFiles.length;
}, },
commitButtonText() { commitMessageCount() {
return n__('Commit %d file', 'Commit %d files', this.changedFiles.length); return this.commitMessage.length;
}, },
}, },
methods: { methods: {
...@@ -77,12 +85,20 @@ export default { ...@@ -77,12 +85,20 @@ export default {
this.submitCommitsLoading = false; this.submitCommitsLoading = false;
}); });
}, },
toggleCollapsed() {
this.collapsed = !this.collapsed;
},
}, },
}; };
</script> </script>
<template> <template>
<div id="commit-area"> <div
class="multi-file-commit-panel"
:class="{
'is-collapsed': collapsed,
}"
>
<popup-dialog <popup-dialog
v-if="showNewBranchDialog" v-if="showNewBranchDialog"
:primary-button-label="__('Create new branch')" :primary-button-label="__('Create new branch')"
...@@ -92,78 +108,71 @@ export default { ...@@ -92,78 +108,71 @@ export default {
@toggle="showNewBranchDialog = false" @toggle="showNewBranchDialog = false"
@submit="makeCommit(true)" @submit="makeCommit(true)"
/> />
<button
v-if="collapsed"
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn is-collapsed prepend-top-10 append-bottom-10"
@click="toggleCollapsed"
>
<i
aria-hidden="true"
class="fa fa-angle-double-left"
>
</i>
</button>
<commit-files-list
title="Staged"
:file-list="changedFiles"
:collapsed="collapsed"
@toggleCollapsed="toggleCollapsed"
/>
<form <form
class="form-horizontal" class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit()"> @submit.prevent="tryCommit"
<fieldset> v-if="!collapsed"
<div class="form-group"> >
<label class="col-md-4 control-label staged-files"> <div class="multi-file-commit-fieldset">
Staged files ({{changedFiles.length}}) <textarea
</label> class="form-control multi-file-commit-message"
<div class="col-md-6"> name="commit-message"
<ul class="list-unstyled changed-files"> v-model="commitMessage"
<li placeholder="Commit message"
v-for="(file, index) in changedFiles" >
:key="index"> </textarea>
<span class="help-block"> </div>
{{ file.path }} <div class="multi-file-commit-fieldset">
</span> <label
</li> v-tooltip
</ul> title="Create a new merge request with these changes"
</div> data-container="body"
</div> data-placement="top"
<div class="form-group"> >
<label <input
class="col-md-4 control-label" type="checkbox"
for="commit-message"> v-model="startNewMR"
Commit message />
</label> Merge Request
<div class="col-md-6"> </label>
<textarea <button
id="commit-message" type="submit"
class="form-control" :disabled="commitButtonDisabled"
name="commit-message" class="btn btn-default btn-sm append-right-10 prepend-left-10"
v-model="commitMessage"> >
</textarea> <i
</div> v-if="submitCommitsLoading"
</div> class="js-commit-loading-icon fa fa-spinner fa-spin"
<div class="form-group target-branch"> aria-hidden="true"
<label aria-label="loading"
class="col-md-4 control-label" >
for="target-branch"> </i>
Target branch Commit
</label> </button>
<div class="col-md-6"> <div
<span class="help-block"> class="multi-file-commit-message-count"
{{currentBranch}} >
</span> {{ commitMessageCount }}
</div>
</div>
<div class="col-md-offset-4 col-md-6">
<button
type="submit"
:disabled="commitButtonDisabled"
class="btn btn-success">
<i
v-if="submitCommitsLoading"
class="js-commit-loading-icon fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="loading">
</i>
<span class="commit-summary">
{{ commitButtonText }}
</span>
</button>
</div>
<div class="col-md-offset-4 col-md-6">
<div class="checkbox">
<label>
<input type="checkbox" v-model="startNewMR">
<span>Start a <strong>new merge request</strong> with these changes</span>
</label>
</div>
</div> </div>
</fieldset> </div>
</form> </form>
</div> </div>
</template> </template>
...@@ -85,12 +85,11 @@ ...@@ -85,12 +85,11 @@
</td> </td>
<template v-if="!isCollapsed && !isSubmodule"> <template v-if="!isCollapsed && !isSubmodule">
<td class="hidden-sm hidden-xs"> <td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
<a <a
v-if="file.lastCommit.message" v-if="file.lastCommit.message"
@click.stop @click.stop
:href="file.lastCommit.url" :href="file.lastCommit.url"
class="commit-message"
> >
{{ file.lastCommit.message }} {{ file.lastCommit.message }}
</a> </a>
......
...@@ -22,12 +22,12 @@ export default { ...@@ -22,12 +22,12 @@ export default {
<template> <template>
<div <div
v-if="showButtons" v-if="showButtons"
class="repo-file-buttons" class="multi-file-editor-btn-group"
> >
<a <a
:href="activeFile.rawPath" :href="activeFile.rawPath"
target="_blank" target="_blank"
class="btn btn-default raw" class="btn btn-default btn-sm raw"
rel="noopener noreferrer"> rel="noopener noreferrer">
{{ rawDownloadButtonLabel }} {{ rawDownloadButtonLabel }}
</a> </a>
...@@ -38,17 +38,17 @@ export default { ...@@ -38,17 +38,17 @@ export default {
aria-label="File actions"> aria-label="File actions">
<a <a
:href="activeFile.blamePath" :href="activeFile.blamePath"
class="btn btn-default blame"> class="btn btn-default btn-sm blame">
Blame Blame
</a> </a>
<a <a
:href="activeFile.commitsPath" :href="activeFile.commitsPath"
class="btn btn-default history"> class="btn btn-default btn-sm history">
History History
</a> </a>
<a <a
:href="activeFile.permalink" :href="activeFile.permalink"
class="btn btn-default permalink"> class="btn btn-default btn-sm permalink">
Permalink Permalink
</a> </a>
</div> </div>
......
...@@ -32,10 +32,12 @@ export default { ...@@ -32,10 +32,12 @@ export default {
</script> </script>
<template> <template>
<div class="blob-viewer-container"> <div>
<div <div
v-if="!activeFile.renderError" v-if="!activeFile.renderError"
v-html="activeFile.html"> v-html="activeFile.html"
class="multi-file-preview-holder"
>
</div> </div>
<div <div
v-else-if="activeFile.tempFile" v-else-if="activeFile.tempFile"
......
...@@ -44,17 +44,13 @@ export default { ...@@ -44,17 +44,13 @@ export default {
</script> </script>
<template> <template>
<div id="sidebar" :class="{'sidebar-mini' : isCollapsed}"> <div class="ide-file-list">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th <th
v-if="isCollapsed" v-if="isCollapsed"
class="repo-file-options title"
> >
<strong class="clgray">
{{ projectName }}
</strong>
</th> </th>
<template v-else> <template v-else>
<th class="name multi-file-table-col-name"> <th class="name multi-file-table-col-name">
...@@ -79,7 +75,7 @@ export default { ...@@ -79,7 +75,7 @@ export default {
:key="n" :key="n"
/> />
<repo-file <repo-file
v-for="(file, index) in treeList" v-for="file in treeList"
:key="file.key" :key="file.key"
:file="file" :file="file"
/> />
......
...@@ -36,27 +36,32 @@ export default { ...@@ -36,27 +36,32 @@ export default {
<template> <template>
<li <li
:class="{ active : tab.active }"
@click="setFileActive(tab)" @click="setFileActive(tab)"
> >
<button <button
type="button" type="button"
class="close-btn" class="multi-file-tab-close"
@click.stop.prevent="closeFile({ file: tab })" @click.stop.prevent="closeFile({ file: tab })"
:aria-label="closeLabel"> :aria-label="closeLabel"
:class="{
'modified': tab.changed,
}"
:disabled="tab.changed"
>
<i <i
class="fa" class="fa"
:class="changedClass" :class="changedClass"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
</button> </button>
<a <div
href="#" class="multi-file-tab"
class="repo-tab" :class="{active : tab.active }"
:title="tab.url" :title="tab.url"
@click.prevent.stop="setFileActive(tab)"> >
{{tab.name}} {{ tab.name }}
</a> </div>
</li> </li>
</template> </template>
...@@ -16,14 +16,12 @@ ...@@ -16,14 +16,12 @@
<template> <template>
<ul <ul
id="tabs" class="multi-file-tabs list-unstyled append-bottom-0"
class="list-unstyled"
> >
<repo-tab <repo-tab
v-for="tab in openFiles" v-for="tab in openFiles"
:key="tab.id" :key="tab.id"
:tab="tab" :tab="tab"
/> />
<li class="tabs-divider" />
</ul> </ul>
</template> </template>
...@@ -34,3 +34,7 @@ export const canEditFile = (state) => { ...@@ -34,3 +34,7 @@ export const canEditFile = (state) => {
openedFiles.length && openedFiles.length &&
(currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary); (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
}; };
export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
...@@ -35,270 +35,240 @@ ...@@ -35,270 +35,240 @@
} }
} }
.repository-view { .multi-file {
border: 1px solid $border-color; display: flex;
border-radius: $border-radius-default; height: calc(100vh - 145px);
color: $almost-black; border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
.code.white pre .hll {
background-color: $well-light-border !important; &.is-collapsed {
.ide-file-list {
max-width: 250px;
overflow: scroll;
}
} }
}
.tree-content-holder { .ide-file-list {
display: -webkit-flex; flex: 1;
display: flex;
min-height: 300px; .file {
cursor: pointer;
} }
.tree-content-holder-mini { a {
height: 100vh; color: $gl-text-color;
} }
}
.panel-right { .multi-file-table-col-name,
display: -webkit-flex; .multi-file-table-col-commit-message {
display: flex; white-space: nowrap;
-webkit-flex-direction: column; overflow: hidden;
flex-direction: column; text-overflow: ellipsis;
width: 80%; max-width: 0;
height: 100%; }
.monaco-editor.vs { .multi-file-table-col-name {
.current-line { width: 350px;
border: 0; }
background: $well-light-border;
}
.line-numbers { .multi-file-table-col-commit-message {
cursor: pointer; width: 450px;
}
&:hover { .multi-file-edit-pane {
text-decoration: underline; display: flex;
} flex-direction: column;
} flex: 1;
} border-left: 1px solid $white-dark;
overflow: hidden;
}
.blob-no-preview { .multi-file-tabs {
.vertical-center { display: flex;
justify-content: center; overflow: scroll;
width: 100%; background-color: $white-normal;
} box-shadow: inset 0 -1px $white-dark;
}
&.blob-editor-container { > li {
overflow: hidden; position: relative;
} }
}
.blob-viewer-container { .multi-file-tab {
-webkit-flex: 1; padding: ($gl-padding / 2) ($gl-padding + 12) ($gl-padding / 2) $gl-padding;
flex: 1; background-color: $gray-normal;
overflow: auto; border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
cursor: pointer;
@include str-truncated(150px);
&.active {
background-color: $white-light;
border-bottom-color: $white-light;
}
}
> div, .multi-file-tab-close {
.file-content:not(.wiki) { position: absolute;
display: flex; right: 8px;
} top: 50%;
padding: 0;
background: none;
border: 0;
font-size: $gl-font-size;
color: $gray-darkest;
transform: translateY(-50%);
&:not(.modified):hover,
&:not(.modified):focus {
color: $hint-color;
}
> div, &.modified {
.file-content, color: $indigo-700;
.blob-viewer, }
.line-number, }
.blob-content,
.code {
min-height: 100%;
width: 100%;
}
.line-numbers { .multi-file-edit-pane-content {
min-width: 44px; flex: 1;
} height: 0;
}
.blob-content { .multi-file-editor-btn-group {
flex: 1; padding: $grid-size;
overflow-x: auto; border-top: 1px solid $white-dark;
} }
// Not great, but this is to deal with our current output
.multi-file-preview-holder {
height: 100%;
overflow: scroll;
& {
.blob-viewer {
height: 100%;
} }
#tabs { .file-content.code {
position: relative;
flex-shrink: 0;
display: flex; display: flex;
width: 100%;
padding-left: 0; i {
margin-bottom: 0; margin-left: -10px;
white-space: nowrap;
overflow-y: hidden;
overflow-x: auto;
li {
position: relative;
background: $gray-normal;
padding: #{$gl-padding / 2} $gl-padding;
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
cursor: pointer;
&.active {
background: $white-light;
border-bottom: 0;
}
a {
@include str-truncated(100px);
color: $gl-text-color;
vertical-align: middle;
text-decoration: none;
margin-right: 12px;
&:focus {
outline: none;
}
}
.close-btn {
position: absolute;
right: 8px;
top: 50%;
padding: 0;
background: none;
border: 0;
font-size: $gl-font-size;
transform: translateY(-50%);
}
.close-icon:hover {
color: $hint-color;
}
.close-icon,
.unsaved-icon {
color: $gray-darkest;
}
.unsaved-icon {
color: $brand-success;
}
&.tabs-divider {
width: 100%;
background-color: $white-light;
border-right: 0;
border-top-right-radius: 2px;
}
} }
} }
.repo-file-buttons { .line-numbers {
background-color: $white-light; min-width: 50px;
padding: 5px 10px;
border-top: 1px solid $white-normal;
} }
#binary-viewer { .file-content,
height: 80vh; .line-numbers,
overflow: auto; .blob-content,
margin: 0; .code {
min-height: 100%;
}
}
}
.blob-viewer { .multi-file-commit-panel {
padding-top: 20px; display: flex;
padding-left: 20px; flex-direction: column;
} height: 100%;
width: 290px;
padding: $gl-padding;
background-color: $gray-light;
border-left: 1px solid $white-dark;
&.is-collapsed {
width: 60px;
padding: 0;
}
}
.binary-unknown { .multi-file-commit-panel-section {
text-align: center; display: flex;
padding-top: 100px; flex-direction: column;
background: $gray-light; flex: 1;
height: 100%; }
font-size: 17px;
span { .multi-file-commit-panel-header {
display: block; display: flex;
} align-items: center;
} padding: 0 0 12px 0;
} margin-bottom: 12px;
} border-bottom: 1px solid $white-dark;
#commit-area { &.is-collapsed {
background: $gray-light; border-bottom: 1px solid $white-dark;
padding: 20px;
.help-block { svg {
padding-top: 7px; margin-left: auto;
margin-top: 0; margin-right: auto;
} }
} }
}
#view-toggler { .multi-file-commit-panel-collapse-btn {
height: 41px; padding-top: 0;
position: relative; padding-bottom: 0;
display: block; margin-left: auto;
border-bottom: 1px solid $white-normal; font-size: 20px;
background: $white-light;
margin-top: -5px;
}
#binary-viewer { &.is-collapsed {
img { margin-right: auto;
max-width: 100%;
}
} }
}
#sidebar { .multi-file-commit-list {
flex: 1; flex: 1;
height: 100%; overflow: scroll;
}
&.sidebar-mini {
width: 20%;
border-right: 1px solid $white-normal;
overflow: auto;
}
.table { .multi-file-commit-list-item {
margin-bottom: 0; display: flex;
} align-items: center;
}
tr { .multi-file-addition {
.repo-file-options { fill: $green-500;
padding: 2px 16px; }
width: 100%;
}
.title { .multi-file-modified {
font-size: 10px; fill: $orange-500;
text-transform: uppercase; }
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
.file-icon { .multi-file-commit-list-collapsed {
margin-right: 5px; display: flex;
} flex-direction: column;
td { > svg {
white-space: nowrap; margin-left: auto;
} margin-right: auto;
} }
}
.file { .multi-file-commit-list-path {
cursor: pointer; @include str-truncated(100%);
} }
a { .multi-file-commit-form {
@include str-truncated(250px); padding-top: 12px;
color: $almost-black; border-top: 1px solid $white-dark;
}
}
} }
.render-error { .multi-file-commit-fieldset {
min-height: calc(100vh - 62px); display: flex;
align-items: center;
padding-bottom: 12px;
p { .btn {
width: 100%; flex: 1;
} }
} }
.multi-file-table-col-name { .multi-file-commit-message.form-control {
width: 350px; height: 80px;
resize: none;
} }
...@@ -11,6 +11,6 @@ ...@@ -11,6 +11,6 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'repo' = webpack_bundle_tag 'repo'
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } %div{ class: [(container_class unless show_new_repo?), ("limit-container-width" unless fluid_layout)] }
= render 'projects/last_push' = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
- @no_container = true;
#repo{ data: { root: @path.empty?.to_s, #repo{ data: { root: @path.empty?.to_s,
root_url: project_tree_path(project), root_url: project_tree_path(project),
url: content_url, url: content_url,
......
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