提交 249b5015 编写于 作者: P Phil Hughes

Merge branch '34312-eslint-vue-plugin' into 'master'

Resolve "Add eslint-vue-plugin to our stack"

Closes #34312

See merge request gitlab-org/gitlab-ce!16210
......@@ -13,7 +13,8 @@ engines:
exclude_paths:
- "lib/api/v3/*"
eslint:
enabled: true
# eslint-plugin-vue is locked to version 2 in codeclimate, we need version 4
enabled: false
rubocop:
enabled: true
channel: "gitlab-rubocop-0-52"
......
......@@ -4,7 +4,10 @@
"browser": true,
"es6": true
},
"extends": "airbnb-base",
"extends": [
"airbnb-base",
"plugin:vue/recommended"
],
"globals": {
"__webpack_public_path__": true,
"_": false,
......@@ -12,7 +15,9 @@
"gon": false,
"localStorage": false
},
"parser": "babel-eslint",
"parserOptions": {
"parser": "babel-eslint"
},
"plugins": [
"filenames",
"import",
......@@ -20,7 +25,7 @@
"promise"
],
"settings": {
"html/html-extensions": [".html", ".html.raw", ".vue"],
"html/html-extensions": [".html", ".html.raw"],
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
......@@ -32,6 +37,15 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__"]}]
"no-underscore-dangle": ["error", { "allow": ["__"]}],
"vue/html-self-closing": ["error", {
"html": {
"void": "always",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}]
}
}
......@@ -8,6 +8,9 @@ export default () => {
new Vue({
el,
components: {
notebookLab,
},
data() {
return {
error: false,
......@@ -16,8 +19,41 @@ export default () => {
json: {},
};
},
components: {
notebookLab,
mounted() {
if (gon.katex_css_url) {
const katexStyles = document.createElement('link');
katexStyles.setAttribute('rel', 'stylesheet');
katexStyles.setAttribute('href', gon.katex_css_url);
document.head.appendChild(katexStyles);
}
if (gon.katex_js_url) {
const katexScript = document.createElement('script');
katexScript.addEventListener('load', () => {
this.loadFile();
});
katexScript.setAttribute('src', gon.katex_js_url);
document.head.appendChild(katexScript);
} else {
this.loadFile();
}
},
methods: {
loadFile() {
axios.get(el.dataset.endpoint)
.then(res => res.data)
.then((data) => {
this.json = data;
this.loading = false;
})
.catch((e) => {
if (e.status !== 200) {
this.loadError = true;
}
this.error = true;
});
},
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
......@@ -46,41 +82,5 @@ export default () => {
</p>
</div>
`,
methods: {
loadFile() {
axios.get(el.dataset.endpoint)
.then(res => res.data)
.then((data) => {
this.json = data;
this.loading = false;
})
.catch((e) => {
if (e.status !== 200) {
this.loadError = true;
}
this.error = true;
});
},
},
mounted() {
if (gon.katex_css_url) {
const katexStyles = document.createElement('link');
katexStyles.setAttribute('rel', 'stylesheet');
katexStyles.setAttribute('href', gon.katex_css_url);
document.head.appendChild(katexStyles);
}
if (gon.katex_js_url) {
const katexScript = document.createElement('script');
katexScript.addEventListener('load', () => {
this.loadFile();
});
katexScript.setAttribute('src', gon.katex_js_url);
document.head.appendChild(katexScript);
} else {
this.loadFile();
}
},
});
};
......@@ -7,6 +7,9 @@ export default () => {
return new Vue({
el,
components: {
pdfLab,
},
data() {
return {
error: false,
......@@ -15,9 +18,6 @@ export default () => {
pdf: el.dataset.endpoint,
};
},
components: {
pdfLab,
},
methods: {
onLoad() {
this.loading = false;
......
......@@ -171,19 +171,14 @@ $(() => {
});
gl.IssueBoardsModalAddBtn = new Vue({
mixins: [gl.issueBoards.ModalMixins],
el: document.getElementById('js-add-issues-btn'),
mixins: [gl.issueBoards.ModalMixins],
data() {
return {
modal: ModalStore.store,
store: Store.state,
};
},
watch: {
disabled() {
this.updateTooltip();
},
},
computed: {
disabled() {
if (!this.store) {
......@@ -199,6 +194,14 @@ $(() => {
return '';
},
},
watch: {
disabled() {
this.updateTooltip();
},
},
mounted() {
this.updateTooltip();
},
methods: {
updateTooltip() {
const $tooltip = $(this.$refs.addIssuesButton);
......@@ -217,9 +220,6 @@ $(() => {
}
},
},
mounted() {
this.updateTooltip();
},
template: `
<div class="board-extra-actions">
<button
......
......@@ -10,12 +10,30 @@ export default {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
list: Object,
issue: Object,
issueLinkBase: String,
disabled: Boolean,
index: Number,
rootPath: String,
list: {
type: Object,
default: () => ({}),
},
issue: {
type: Object,
default: () => ({}),
},
issueLinkBase: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
index: {
type: Number,
default: 0,
},
rootPath: {
type: String,
default: '',
},
},
data() {
return {
......@@ -54,8 +72,13 @@ export default {
</script>
<template>
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
<li
class="card"
:class="{
'user-can-drag': !disabled && issue.id,
'is-disabled': disabled || !issue.id,
'is-active': issueDetailVisible
}"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
......@@ -66,6 +89,7 @@ export default {
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
:update-filters="true" />
:update-filters="true"
/>
</li>
</template>
<script>
import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import {
APPLICATION_NOT_INSTALLABLE,
APPLICATION_SCHEDULED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
APPLICATION_ERROR,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
} from '../constants';
/* eslint-disable vue/require-default-prop */
import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import {
APPLICATION_NOT_INSTALLABLE,
APPLICATION_SCHEDULED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
APPLICATION_ERROR,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
} from '../constants';
export default {
props: {
id: {
type: String,
required: true,
export default {
components: {
loadingButton,
},
title: {
type: String,
required: true,
props: {
id: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
titleLink: {
type: String,
required: false,
},
description: {
type: String,
required: true,
},
status: {
type: String,
required: false,
},
statusReason: {
type: String,
required: false,
},
requestStatus: {
type: String,
required: false,
},
requestReason: {
type: String,
required: false,
},
},
titleLink: {
type: String,
required: false,
},
description: {
type: String,
required: true,
},
status: {
type: String,
required: false,
},
statusReason: {
type: String,
required: false,
},
requestStatus: {
type: String,
required: false,
},
requestReason: {
type: String,
required: false,
},
},
components: {
loadingButton,
},
computed: {
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
installButtonLoading() {
return !this.status ||
this.status === APPLICATION_SCHEDULED ||
this.status === APPLICATION_INSTALLING ||
this.requestStatus === REQUEST_LOADING;
},
installButtonDisabled() {
// Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
// we already made a request to install and are just waiting for the real-time
// to sync up.
return (this.status !== APPLICATION_INSTALLABLE && this.status !== APPLICATION_ERROR) ||
this.requestStatus === REQUEST_LOADING ||
this.requestStatus === REQUEST_SUCCESS;
},
installButtonLabel() {
let label;
if (
this.status === APPLICATION_NOT_INSTALLABLE ||
this.status === APPLICATION_INSTALLABLE ||
this.status === APPLICATION_ERROR
) {
label = s__('ClusterIntegration|Install');
} else if (this.status === APPLICATION_SCHEDULED || this.status === APPLICATION_INSTALLING) {
label = s__('ClusterIntegration|Installing');
} else if (this.status === APPLICATION_INSTALLED) {
label = s__('ClusterIntegration|Installed');
}
computed: {
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
installButtonLoading() {
return !this.status ||
this.status === APPLICATION_SCHEDULED ||
this.status === APPLICATION_INSTALLING ||
this.requestStatus === REQUEST_LOADING;
},
installButtonDisabled() {
// Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
// we already made a request to install and are just waiting for the real-time
// to sync up.
return (this.status !== APPLICATION_INSTALLABLE
&& this.status !== APPLICATION_ERROR) ||
this.requestStatus === REQUEST_LOADING ||
this.requestStatus === REQUEST_SUCCESS;
},
installButtonLabel() {
let label;
if (
this.status === APPLICATION_NOT_INSTALLABLE ||
this.status === APPLICATION_INSTALLABLE ||
this.status === APPLICATION_ERROR
) {
label = s__('ClusterIntegration|Install');
} else if (this.status === APPLICATION_SCHEDULED ||
this.status === APPLICATION_INSTALLING) {
label = s__('ClusterIntegration|Installing');
} else if (this.status === APPLICATION_INSTALLED) {
label = s__('ClusterIntegration|Installed');
}
return label;
},
hasError() {
return this.status === APPLICATION_ERROR || this.requestStatus === REQUEST_FAILURE;
},
generalErrorDescription() {
return sprintf(
s__('ClusterIntegration|Something went wrong while installing %{title}'), {
title: this.title,
},
);
return label;
},
hasError() {
return this.status === APPLICATION_ERROR ||
this.requestStatus === REQUEST_FAILURE;
},
generalErrorDescription() {
return sprintf(
s__('ClusterIntegration|Something went wrong while installing %{title}'), {
title: this.title,
},
);
},
},
},
methods: {
installClicked() {
eventHub.$emit('installApplication', this.id);
methods: {
installClicked() {
eventHub.$emit('installApplication', this.id);
},
},
},
};
};
</script>
<template>
......
<script>
import _ from 'underscore';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import _ from 'underscore';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
export default {
props: {
applications: {
type: Object,
required: false,
default: () => ({}),
export default {
components: {
applicationRow,
},
helpPath: {
type: String,
required: false,
props: {
applications: {
type: Object,
required: false,
default: () => ({}),
},
helpPath: {
type: String,
required: false,
default: '',
},
},
},
components: {
applicationRow,
},
computed: {
generalApplicationDescription() {
return sprintf(
_.escape(s__('ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}')), {
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`,
},
false,
);
},
helmTillerDescription() {
return _.escape(s__(
`ClusterIntegration|Helm streamlines installing and managing Kubernets applications.
Tiller runs inside of your Kubernetes Cluster, and manages
releases of your charts.`,
));
},
ingressDescription() {
const descriptionParagraph = _.escape(s__(
`ClusterIntegration|Ingress gives you a way to route requests to services based on the
request host or path, centralizing a number of services into a single entrypoint.`,
));
computed: {
generalApplicationDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Install applications on your cluster.
Read more about %{helpLink}`)),
{
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`,
},
false,
);
},
helmTillerDescription() {
return _.escape(s__(
`ClusterIntegration|Helm streamlines installing and managing Kubernets applications.
Tiller runs inside of your Kubernetes Cluster, and manages
releases of your charts.`,
));
},
ingressDescription() {
const descriptionParagraph = _.escape(s__(
`ClusterIntegration|Ingress gives you a way to route requests to services based on the
request host or path, centralizing a number of services into a single entrypoint.`,
));
const extraCostParagraph = sprintf(
_.escape(s__('ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}')), {
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GKE pricing'))}
</a>`,
},
false,
);
const extraCostParagraph = sprintf(
_.escape(s__(`ClusterIntegration|%{boldNotice} This will add some
extra resources like a load balancer,
which incur additional costs. See %{pricingLink}`)),
{
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GKE pricing'))}
</a>`,
},
false,
);
return `
<p>
${descriptionParagraph}
</p>
<p class="append-bottom-0">
${extraCostParagraph}
</p>
`;
},
gitlabRunnerDescription() {
return _.escape(s__(
`ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
and send the results back to GitLab.`,
));
},
prometheusDescription() {
return sprintf(
_.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
</a>`,
},
false,
);
return `
<p>
${descriptionParagraph}
</p>
<p class="append-bottom-0">
${extraCostParagraph}
</p>
`;
},
gitlabRunnerDescription() {
return _.escape(s__(
`ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
and send the results back to GitLab.`,
));
},
prometheusDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`)),
{
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
</a>`,
},
false,
);
},
},
},
};
};
</script>
<template>
......@@ -107,26 +116,29 @@ export default {
:request-reason="applications.helm.requestReason"
/>
<application-row
id="ingress"
:title="applications.ingress.title"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
:description="ingressDescription"
:status="applications.ingress.status"
:status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
/>
<application-row
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
/>
<!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
id="ingress"
:title="applications.ingress.title"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
:description="ingressDescription"
:status="applications.ingress.status"
:status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
/>
<application-row
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
/>
<!--
NOTE: Don't forget to update `clusters.scss`
min-height for this block and uncomment `application_spec` tests
-->
<!-- Add GitLab Runner row, all other plumbing is complete -->
</div>
</div>
......
......@@ -94,7 +94,7 @@ export default class ImageFile {
});
return [maxWidth, maxHeight];
}
// eslint-disable-next-line
views = {
'two-up': function() {
return $('.two-up.view .wrap', this.file).each((function(_this) {
......
......@@ -4,6 +4,10 @@
import pipelinesMixin from '../../pipelines/mixins/pipelines';
export default {
mixins: [
pipelinesMixin,
],
props: {
endpoint: {
type: String,
......@@ -31,9 +35,6 @@
default: 'child',
},
},
mixins: [
pipelinesMixin,
],
data() {
const store = new PipelineStore();
......@@ -95,28 +96,29 @@
label="Loading pipelines"
size="3"
v-if="isLoading"
/>
/>
<empty-state
v-if="shouldRenderEmptyState"
:help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath"
/>
/>
<error-state
v-if="shouldRenderErrorState"
:error-state-svg-path="errorStateSvgPath"
/>
/>
<div
class="table-holder"
v-if="shouldRenderTable">
v-if="shouldRenderTable"
>
<pipelines-table-component
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
/>
</div>
</div>
</template>
......@@ -26,28 +26,34 @@
class="js-ca-dismiss-button dismiss-button"
type="button"
:aria-label="__('Dismiss Cycle Analytics introduction box')"
@click="dismissOverviewDialog">
@click="dismissOverviewDialog"
>
<i
class="fa fa-times"
aria-hidden="true">
</i>
</button>
<div class="svg-container" v-html="iconCycleAnalyticsSplash">
<div
class="svg-container"
v-html="iconCycleAnalyticsSplash"
>
</div>
<div class="inner-content">
<h4>
{{__('Introducing Cycle Analytics')}}
{{ __('Introducing Cycle Analytics') }}
</h4>
<p>
{{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
{{ __(`Cycle Analytics gives an overview
of how much time it takes to go from idea to production in your project.`) }}
</p>
<p>
<a
:href="documentationLink"
target="_blank"
rel="nofollow"
class="btn">
{{__('Read more')}}
class="btn"
>
{{ __('Read more') }}
</a>
</p>
</div>
......
......@@ -2,25 +2,34 @@
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
props: {
count: {
type: Number,
required: true,
},
},
directives: {
tooltip,
},
};
</script>
<template>
<span v-if="count === 50" class="events-info pull-right">
<span
v-if="count === 50"
class="events-info pull-right"
>
<i
class="fa fa-warning"
v-tooltip
aria-hidden="true"
:title="n__('Limited to showing %d event at most', 'Limited to showing %d events at most', 50)"
data-placement="top"></i>
:title="n__(
'Limited to showing %d event at most',
'Limited to showing %d events at most',
50
)"
data-placement="top"
>
</i>
{{ n__('Showing %d event', 'Showing %d events', 50) }}
</span>
</template>
......@@ -4,15 +4,21 @@
import totalTime from './total_time_component.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
limitWarning,
totalTime,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
};
</script>
<template>
......@@ -22,28 +28,44 @@
<limit-warning :count="items.length" />
</div>
<ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item">
<li
v-for="(mergeRequest, i) in items"
:key="i"
class="stage-event-item"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<user-avatar-image :img-src="mergeRequest.author.avatarUrl" />
<h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url">
{{ mergeRequest.title }}
</a>
</h5>
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
<a
:href="mergeRequest.url"
class="issue-link">
!{{ mergeRequest.iid }}
</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
<a
:href="mergeRequest.url"
class="issue-date">
{{ mergeRequest.createdAt }}
</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
<a
:href="mergeRequest.author.webUrl"
class="issue-author-link">
{{ mergeRequest.author.name }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="mergeRequest.totalTime"></total-time>
<total-time :time="mergeRequest.totalTime" />
</div>
</li>
</ul>
......
......@@ -4,15 +4,21 @@
import totalTime from './total_time_component.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
limitWarning,
totalTime,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
};
</script>
<template>
......@@ -25,30 +31,43 @@
<li
v-for="(issue, i) in items"
:key="i"
class="stage-event-item">
class="stage-event-item"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url">
<a
class="issue-title"
:href="issue.url"
>
{{ issue.title }}
</a>
</h5>
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
<a
:href="issue.url"
class="issue-link"
>#{{ issue.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
<a
:href="issue.url"
class="issue-date"
>{{ issue.createdAt }}</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link">
<a
:href="issue.author.webUrl"
class="issue-author-link"
>
{{ issue.author.name }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="issue.totalTime"/>
<total-time :time="issue.totalTime" />
</div>
</li>
</ul>
......
......@@ -5,15 +5,21 @@
import totalTime from './total_time_component.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
totalTime,
limitWarning,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
computed: {
iconCommit() {
return iconCommit;
......@@ -31,10 +37,11 @@
<li
v-for="(commit, i) in items"
:key="i"
class="stage-event-item">
class="stage-event-item"
>
<div class="item-details item-conmmit-component">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="commit.author.avatarUrl"/>
<user-avatar-image :img-src="commit.author.avatarUrl" />
<h5 class="item-title commit-title">
<a :href="commit.commitUrl">
{{ commit.title }}
......@@ -42,10 +49,20 @@
</h5>
<span>
{{ s__('FirstPushedBy|First') }}
<span class="commit-icon" v-html="iconCommit"></span>
<a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
<span
class="commit-icon"
v-html="iconCommit"
>
</span>
<a
:href="commit.commitUrl"
class="commit-hash-link commit-sha"
>{{ commit.shortSha }}</a>
{{ s__('FirstPushedBy|pushed by') }}
<a :href="commit.author.webUrl" class="commit-author-link">
<a
:href="commit.author.webUrl"
class="commit-author-link"
>
{{ commit.author.name }}
</a>
</span>
......
......@@ -5,16 +5,22 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
};
</script>
<template>
......@@ -27,7 +33,8 @@
<li
v-for="(mergeRequest, i) in items"
:key="i"
class="stage-event-item">
class="stage-event-item"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
......@@ -36,34 +43,52 @@
{{ mergeRequest.title }}
</a>
</h5>
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
<a
:href="mergeRequest.url"
class="issue-link"
>!{{ mergeRequest.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
<a
:href="mergeRequest.url"
class="issue-date"
>{{ mergeRequest.createdAt }}</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
<a
:href="mergeRequest.author.webUrl"
class="issue-author-link"
>{{ mergeRequest.author.name }}</a>
</span>
<template v-if="mergeRequest.state === 'closed'">
<span class="merge-request-state">
<i class="fa fa-ban"></i>
<i
class="fa fa-ban"
aria-hidden="true"
>
</i>
{{ mergeRequest.state.toUpperCase() }}
</span>
</template>
<template v-else>
<span class="merge-request-branch" v-if="mergeRequest.branch">
<span
class="merge-request-branch"
v-if="mergeRequest.branch"
>
<icon
name="fork"
:size="16">
</icon>
<a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
:size="16"
/>
<a :href="mergeRequest.branch.url">
{{ mergeRequest.branch.name }}
</a>
</span>
</template>
</div>
<div class="item-time">
<total-time :time="mergeRequest.totalTime"/>
<total-time :time="mergeRequest.totalTime" />
</div>
</li>
</ul>
......
......@@ -6,16 +6,22 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
computed: {
iconBranch() {
return iconBranch;
......@@ -33,30 +39,58 @@
<li
v-for="(build, i) in items"
class="stage-event-item item-build-component"
:key="i">
:key="i"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<a
:href="build.url"
class="pipeline-id"
>
#{{ build.id }}
</a>
<icon
name="fork"
:size="16">
</icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
:size="16"
/>
<a
:href="build.branch.url"
class="ref-name"
>
{{ build.branch.name }}
</a>
<span
class="icon-branch"
v-html="iconBranch"
>
</span>
<a
:href="build.commitUrl"
class="commit-sha"
>
{{ build.shortSha }}
</a>
</h5>
<span>
<a :href="build.url" class="build-date">{{ build.date }}</a>
<a
:href="build.url"
class="build-date"
>
{{ build.date }}
</a>
{{ s__('ByAuthor|by') }}
<a :href="build.author.webUrl" class="issue-author-link">
<a
:href="build.author.webUrl"
class="issue-author-link"
>
{{ build.author.name }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="build.totalTime"/>
<total-time :time="build.totalTime" />
</div>
</li>
</ul>
......
......@@ -6,15 +6,21 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
computed: {
iconBuildStatus() {
return iconBuildStatus;
......@@ -35,29 +41,59 @@
<li
v-for="(build, i) in items"
:key="i"
class="stage-event-item item-build-component">
class="stage-event-item item-build-component"
>
<div class="item-details">
<h5 class="item-title">
<span class="icon-build-status" v-html="iconBuildStatus"></span>
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
<span
class="icon-build-status"
v-html="iconBuildStatus"
>
</span>
<a
:href="build.url"
class="item-build-name"
>
{{ build.name }}
</a>
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<a
:href="build.url"
class="pipeline-id"
>
#{{ build.id }}
</a>
<icon
name="fork"
:size="16">
</icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
:size="16"
/>
<a
:href="build.branch.url"
class="ref-name"
>
{{ build.branch.name }}
</a>
<span
class="icon-branch"
v-html="iconBranch"
>
</span>
<a
:href="build.commitUrl"
class="commit-sha">
{{ build.shortSha }}
</a>
</h5>
<span>
<a :href="build.url" class="issue-date">
<a
:href="build.url"
class="issue-date">
{{ build.date }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="build.totalTime"/>
<total-time :time="build.totalTime" />
</div>
</li>
</ul>
......
......@@ -17,13 +17,33 @@
<template>
<span class="total-time">
<template v-if="hasData">
<template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
<template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
<template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
<template v-if="time.seconds && hasData === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
<template v-if="time.days">
{{ time.days }}
<span>
{{ n__('day', 'days', time.days) }}
</span>
</template>
<template v-if="time.hours">
{{ time.hours }}
<span>
{{ n__('Time|hr', 'Time|hrs', time.hours) }}
</span>
</template>
<template v-if="time.mins && !time.days">
{{ time.mins }}
<span>
{{ n__('Time|min', 'Time|mins', time.mins) }}
</span>
</template>
<template v-if="time.seconds && hasData === 1 || time.seconds === 0">
{{ time.seconds }}
<span>
{{ s__('Time|s') }}
</span>
</template>
</template>
<template v-else>
--
</template>
</span>
</span>
</template>
......@@ -20,6 +20,16 @@ $(() => {
gl.cycleAnalyticsApp = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
components: {
banner,
'stage-issue-component': stageComponent,
'stage-plan-component': stagePlanComponent,
'stage-code-component': stageCodeComponent,
'stage-test-component': stageTestComponent,
'stage-review-component': stageReviewComponent,
'stage-staging-component': stageStagingComponent,
'stage-production-component': stageComponent,
},
data() {
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
const cycleAnalyticsService = new CycleAnalyticsService({
......@@ -43,16 +53,6 @@ $(() => {
return this.store.currentActiveStage();
},
},
components: {
banner,
'stage-issue-component': stageComponent,
'stage-plan-component': stagePlanComponent,
'stage-code-component': stageCodeComponent,
'stage-test-component': stageTestComponent,
'stage-review-component': stageReviewComponent,
'stage-staging-component': stageStagingComponent,
'stage-production-component': stageComponent,
},
created() {
this.fetchCycleAnalyticsData();
},
......
......@@ -3,10 +3,8 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
data() {
return {
isLoading: false,
};
components: {
loadingIcon,
},
props: {
deployKey: {
......@@ -23,11 +21,16 @@
default: 'btn-default',
},
},
components: {
loadingIcon,
data() {
return {
isLoading: false,
};
},
computed: {
text() {
return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
},
},
methods: {
doAction() {
this.isLoading = true;
......@@ -37,11 +40,6 @@
});
},
},
computed: {
text() {
return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
},
},
};
</script>
......
......@@ -7,11 +7,9 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
data() {
return {
isLoading: false,
store: new DeployKeysStore(),
};
components: {
keysPanel,
loadingIcon,
},
props: {
endpoint: {
......@@ -19,6 +17,12 @@
required: true,
},
},
data() {
return {
isLoading: false,
store: new DeployKeysStore(),
};
},
computed: {
hasKeys() {
return Object.keys(this.keys).length;
......@@ -27,9 +31,20 @@
return this.store.keys;
},
},
components: {
keysPanel,
loadingIcon,
created() {
this.service = new DeployKeysService(this.endpoint);
eventHub.$on('enable.key', this.enableKey);
eventHub.$on('remove.key', this.disableKey);
eventHub.$on('disable.key', this.disableKey);
},
mounted() {
this.fetchKeys();
},
beforeDestroy() {
eventHub.$off('enable.key', this.enableKey);
eventHub.$off('remove.key', this.disableKey);
eventHub.$off('disable.key', this.disableKey);
},
methods: {
fetchKeys() {
......@@ -59,21 +74,6 @@
}
},
},
created() {
this.service = new DeployKeysService(this.endpoint);
eventHub.$on('enable.key', this.enableKey);
eventHub.$on('remove.key', this.disableKey);
eventHub.$on('disable.key', this.disableKey);
},
mounted() {
this.fetchKeys();
},
beforeDestroy() {
eventHub.$off('enable.key', this.enableKey);
eventHub.$off('remove.key', this.disableKey);
eventHub.$off('disable.key', this.disableKey);
},
};
</script>
......
......@@ -3,6 +3,9 @@
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
components: {
actionBtn,
},
props: {
deployKey: {
type: Object,
......@@ -17,9 +20,6 @@
required: true,
},
},
components: {
actionBtn,
},
computed: {
timeagoDate() {
return getTimeago().format(this.deployKey.created_at);
......@@ -61,9 +61,10 @@
</div>
<div class="deploy-key-content prepend-left-default deploy-key-projects">
<a
v-for="project in deployKey.projects"
v-for="(project, i) in deployKey.projects"
class="label deploy-project-label"
:href="project.full_path"
:key="i"
>
{{ project.full_name }}
</a>
......
......@@ -2,6 +2,9 @@
import key from './key.vue';
export default {
components: {
key,
},
props: {
title: {
type: String,
......@@ -25,9 +28,6 @@
required: true,
},
},
components: {
key,
},
};
</script>
......@@ -37,12 +37,14 @@
{{ title }}
({{ keys.length }})
</h5>
<ul class="well-list"
<ul
class="well-list"
v-if="keys.length"
>
<li
v-for="deployKey in keys"
:key="deployKey.id">
:key="deployKey.id"
>
<key
:deploy-key="deployKey"
:store="store"
......
......@@ -3,14 +3,14 @@ import deployKeysApp from './components/app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: document.getElementById('js-deploy-keys'),
components: {
deployKeysApp,
},
data() {
return {
endpoint: this.$options.el.dataset.endpoint,
};
},
components: {
deployKeysApp,
},
render(createElement) {
return createElement('deploy-keys-app', {
props: {
......
......@@ -4,6 +4,11 @@
import environmentTable from '../components/environments_table.vue';
export default {
components: {
environmentTable,
loadingIcon,
tablePagination,
},
props: {
isLoading: {
type: Boolean,
......@@ -26,12 +31,6 @@
required: true,
},
},
components: {
environmentTable,
loadingIcon,
tablePagination,
},
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
......@@ -47,7 +46,7 @@
label="Loading environments"
v-if="isLoading"
size="3"
/>
/>
<slot name="emptyState"></slot>
......@@ -59,13 +58,13 @@
:environments="environments"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
/>
/>
<table-pagination
v-if="pagination && pagination.totalPages > 1"
:change="onChangePage"
:pageInfo="pagination"
/>
:page-info="pagination"
/>
</div>
</div>
</template>
<script>
export default {
name: 'environmentsEmptyState',
name: 'EnvironmentsEmptyState',
props: {
newPath: {
type: String,
......@@ -21,21 +21,23 @@
<div class="blank-state-row">
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">
{{s__("Environments|You don't have any environments right now.")}}
{{ s__("Environments|You don't have any environments right now.") }}
</h2>
<p class="blank-state-text">
{{s__("Environments|Environments are places where code gets deployed, such as staging or production.")}}
{{ s__(`Environments|Environments are places where
code gets deployed, such as staging or production.`) }}
<br />
<a :href="helpPath">
{{s__("Environments|Read more about environments")}}
{{ s__("Environments|Read more about environments") }}
</a>
</p>
<a
v-if="canCreateEnvironment"
:href="newPath"
class="btn btn-create js-new-environment-button">
{{s__("Environments|New environment")}}
class="btn btn-create js-new-environment-button"
>
{{ s__("Environments|New environment") }}
</a>
</div>
</div>
......
<script>
import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
props: {
actions: {
type: Array,
required: false,
default: () => [],
export default {
directives: {
tooltip,
},
},
directives: {
tooltip,
},
components: {
loadingIcon,
},
components: {
loadingIcon,
},
props: {
actions: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
playIconSvg,
isLoading: false,
};
},
data() {
return {
playIconSvg,
isLoading: false,
};
},
computed: {
title() {
return 'Deploy to...';
computed: {
title() {
return 'Deploy to...';
},
},
},
methods: {
onClickAction(endpoint) {
this.isLoading = true;
methods: {
onClickAction(endpoint) {
this.isLoading = true;
eventHub.$emit('postAction', endpoint);
},
eventHub.$emit('postAction', endpoint);
},
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
}
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
}
return !action.playable;
return !action.playable;
},
},
},
};
};
</script>
<template>
<div
......@@ -63,27 +62,33 @@ export default {
data-toggle="dropdown"
:title="title"
:aria-label="title"
:disabled="isLoading">
:disabled="isLoading"
>
<span>
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
aria-hidden="true"/>
aria-hidden="true"
>
</i>
<loading-icon v-if="isLoading" />
</span>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<li
v-for="(action, i) in actions"
:key="i">
<button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
:disabled="isActionDisabled(action)"
>
<span v-html="playIconSvg"></span>
<span>
{{action.name}}
{{ action.name }}
</span>
</button>
</li>
......
<script>
import tooltip from '../../vue_shared/directives/tooltip';
import { s__ } from '../../locale';
import tooltip from '../../vue_shared/directives/tooltip';
import { s__ } from '../../locale';
/**
* Renders the external url link in environments table.
*/
export default {
props: {
externalUrl: {
type: String,
required: true,
/**
* Renders the external url link in environments table.
*/
export default {
directives: {
tooltip,
},
props: {
externalUrl: {
type: String,
required: true,
},
},
},
directives: {
tooltip,
},
computed: {
title() {
return s__('Environments|Open');
computed: {
title() {
return s__('Environments|Open');
},
},
},
};
};
</script>
<template>
<a
......@@ -33,9 +32,12 @@ export default {
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title"
:href="externalUrl">
:href="externalUrl"
>
<i
class="fa fa-external-link"
aria-hidden="true" />
aria-hidden="true"
>
</i>
</a>
</template>
<script>
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
import tooltip from '../../vue_shared/directives/tooltip';
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
import tooltip from '../../vue_shared/directives/tooltip';
export default {
props: {
monitoringUrl: {
type: String,
required: true,
export default {
directives: {
tooltip,
},
},
directives: {
tooltip,
},
props: {
monitoringUrl: {
type: String,
required: true,
},
},
computed: {
title() {
return 'Monitoring';
computed: {
title() {
return 'Monitoring';
},
},
},
};
};
</script>
<template>
<a
......@@ -31,10 +31,12 @@ export default {
rel="noopener noreferrer nofollow"
:href="monitoringUrl"
:title="title"
:aria-label="title">
:aria-label="title"
>
<i
class="fa fa-area-chart"
aria-hidden="true"
/>
>
</i>
</a>
</template>
<script>
/**
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
props: {
retryUrl: {
type: String,
default: '',
/**
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
loadingIcon,
},
isLastDeployment: {
type: Boolean,
default: true,
},
},
props: {
retryUrl: {
type: String,
default: '',
},
components: {
loadingIcon,
},
isLastDeployment: {
type: Boolean,
default: true,
},
},
data() {
return {
isLoading: false,
};
},
data() {
return {
isLoading: false,
};
},
methods: {
onClick() {
this.isLoading = true;
methods: {
onClick() {
this.isLoading = true;
eventHub.$emit('postAction', this.retryUrl);
eventHub.$emit('postAction', this.retryUrl);
},
},
},
};
};
</script>
<template>
<button
type="button"
class="btn hidden-xs hidden-sm"
@click="onClick"
:disabled="isLoading">
:disabled="isLoading"
>
<span v-if="isLastDeployment">
{{s__("Environments|Re-deploy")}}
{{ s__("Environments|Re-deploy") }}
</span>
<span v-else>
{{s__("Environments|Rollback")}}
{{ s__("Environments|Rollback") }}
</span>
<loading-icon v-if="isLoading" />
......
<script>
/**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
/**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
props: {
stopUrl: {
type: String,
default: '',
export default {
components: {
loadingIcon,
},
},
directives: {
tooltip,
},
directives: {
tooltip,
},
data() {
return {
isLoading: false,
};
},
props: {
stopUrl: {
type: String,
default: '',
},
},
components: {
loadingIcon,
},
data() {
return {
isLoading: false,
};
},
computed: {
title() {
return 'Stop';
computed: {
title() {
return 'Stop';
},
},
},
methods: {
onClick() {
// eslint-disable-next-line no-alert
if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true;
methods: {
onClick() {
// eslint-disable-next-line no-alert
if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true;
$(this.$el).tooltip('destroy');
$(this.$el).tooltip('destroy');
eventHub.$emit('postAction', this.stopUrl);
}
eventHub.$emit('postAction', this.stopUrl);
}
},
},
},
};
};
</script>
<template>
<button
......@@ -58,10 +58,13 @@ export default {
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
:aria-label="title"
>
<i
class="fa fa-stop stop-env-icon"
aria-hidden="true" />
aria-hidden="true"
>
</i>
<loading-icon v-if="isLoading" />
</button>
</template>
<script>
/**
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
import terminalIconSvg from 'icons/_icon_terminal.svg';
import tooltip from '../../vue_shared/directives/tooltip';
/**
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
import terminalIconSvg from 'icons/_icon_terminal.svg';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
props: {
terminalPath: {
type: String,
required: false,
default: '',
export default {
directives: {
tooltip,
},
},
directives: {
tooltip,
},
props: {
terminalPath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
terminalIconSvg,
};
},
data() {
return {
terminalIconSvg,
};
},
computed: {
title() {
return 'Terminal';
computed: {
title() {
return 'Terminal';
},
},
},
};
};
</script>
<template>
<a
......@@ -40,6 +40,7 @@ export default {
:title="title"
:aria-label="title"
:href="terminalPath"
v-html="terminalIconSvg">
v-html="terminalIconSvg"
>
</a>
</template>
......@@ -7,6 +7,15 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
components: {
emptyState,
},
mixins: [
CIPaginationMixin,
environmentsMixin,
],
props: {
endpoint: {
type: String,
......@@ -37,14 +46,6 @@
required: true,
},
},
components: {
emptyState,
},
mixins: [
CIPaginationMixin,
environmentsMixin,
],
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
......@@ -95,15 +96,17 @@
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="environments"
/>
/>
<div
v-if="canCreateEnvironment && !isLoading"
class="nav-controls">
class="nav-controls"
>
<a
:href="newEnvironmentPath"
class="btn btn-create">
{{s__("Environments|New environment")}}
class="btn btn-create"
>
{{ s__("Environments|New environment") }}
</a>
</div>
</div>
......@@ -116,13 +119,13 @@
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
>
<empty-state
<empty-state
slot="emptyState"
v-if="!isLoading && state.environments.length === 0"
:new-path="newEnvironmentPath"
:help-path="helpPagePath"
:can-create-environment="canCreateEnvironment"
/>
/>
</container>
</div>
</template>
......@@ -30,63 +30,96 @@ export default {
default: false,
},
},
methods: {
folderUrl(model) {
return `${window.location.pathname}/folders/${model.folderName}`;
},
shouldRenderFolderContent(env) {
return env.isFolder &&
env.isOpen &&
env.children &&
env.children.length > 0;
},
},
};
</script>
<template>
<div class="ci-table" role="grid">
<div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-10 environments-name" role="columnheader">
{{s__("Environments|Environment")}}
<div
class="ci-table"
role="grid"
>
<div
class="gl-responsive-table-row table-row-header"
role="row"
>
<div
class="table-section section-10 environments-name"
role="columnheader"
>
{{ s__("Environments|Environment") }}
</div>
<div class="table-section section-10 environments-deploy" role="columnheader">
{{s__("Environments|Deployment")}}
<div
class="table-section section-10 environments-deploy"
role="columnheader"
>
{{ s__("Environments|Deployment") }}
</div>
<div class="table-section section-15 environments-build" role="columnheader">
{{s__("Environments|Job")}}
<div
class="table-section section-15 environments-build"
role="columnheader"
>
{{ s__("Environments|Job") }}
</div>
<div class="table-section section-25 environments-commit" role="columnheader">
{{s__("Environments|Commit")}}
<div
class="table-section section-25 environments-commit"
role="columnheader"
>
{{ s__("Environments|Commit") }}
</div>
<div class="table-section section-10 environments-date" role="columnheader">
{{s__("Environments|Updated")}}
<div
class="table-section section-10 environments-date"
role="columnheader"
>
{{ s__("Environments|Updated") }}
</div>
</div>
<template
v-for="model in environments"
v-bind:model="model">
v-for="(model, i) in environments"
:model="model">
<div
is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
/>
:key="i"
/>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<div v-if="model.isLoadingFolderContent">
<template
v-if="shouldRenderFolderContent(model)"
>
<div
v-if="model.isLoadingFolderContent"
:key="i">
<loading-icon size="2" />
</div>
<template v-else>
<div
is="environment-item"
v-for="children in model.children"
v-for="(children, index) in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
/>
:key="index"
/>
<div>
<div :key="i">
<div class="text-center prepend-top-10">
<a
:href="folderUrl(model)"
class="btn btn-default">
{{s__("Environments|Show all")}}
class="btn btn-default"
>
{{ s__("Environments|Show all") }}
</a>
</div>
</div>
......
......@@ -3,6 +3,10 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
mixins: [
environmentsMixin,
CIPaginationMixin,
],
props: {
endpoint: {
type: String,
......@@ -25,10 +29,6 @@
required: true,
},
},
mixins: [
environmentsMixin,
CIPaginationMixin,
],
methods: {
successCallback(resp) {
this.saveData(resp);
......@@ -40,17 +40,18 @@
<div :class="cssContainerClass">
<div
class="top-area"
v-if="!isLoading">
v-if="!isLoading"
>
<h4 class="js-folder-name environments-folder-name">
{{s__("Environments|Environments")}} / <b>{{folderName}}</b>
{{ s__("Environments|Environments") }} / <b>{{ folderName }}</b>
</h4>
<tabs
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="environments"
/>
/>
</div>
<container
......
......@@ -32,6 +32,9 @@ class RecentSearchesRoot {
const state = this.store.state;
this.vm = new Vue({
el: this.wrapperElement,
components: {
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
},
data() { return state; },
template: `
<recent-searches-dropdown-content
......@@ -40,9 +43,6 @@ class RecentSearchesRoot {
:allowed-keys="allowedKeys"
/>
`,
components: {
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
},
});
}
......
......@@ -42,6 +42,26 @@ export default {
return this.store.getPaginationInfo();
},
},
created() {
this.searchEmptyMessage = this.hideProjects ?
COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleChildren', this.toggleChildren);
eventHub.$on('leaveGroup', this.leaveGroup);
eventHub.$on('updatePagination', this.updatePagination);
eventHub.$on('updateGroups', this.updateGroups);
},
mounted() {
this.fetchAllGroups();
},
beforeDestroy() {
eventHub.$off('fetchPage', this.fetchPage);
eventHub.$off('toggleChildren', this.toggleChildren);
eventHub.$off('leaveGroup', this.leaveGroup);
eventHub.$off('updatePagination', this.updatePagination);
eventHub.$off('updateGroups', this.updateGroups);
},
methods: {
fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
return this.service.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
......@@ -152,26 +172,6 @@ export default {
}
},
},
created() {
this.searchEmptyMessage = this.hideProjects ?
COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleChildren', this.toggleChildren);
eventHub.$on('leaveGroup', this.leaveGroup);
eventHub.$on('updatePagination', this.updatePagination);
eventHub.$on('updateGroups', this.updateGroups);
},
mounted() {
this.fetchAllGroups();
},
beforeDestroy() {
eventHub.$off('fetchPage', this.fetchPage);
eventHub.$off('toggleChildren', this.toggleChildren);
eventHub.$off('leaveGroup', this.leaveGroup);
eventHub.$off('updatePagination', this.updatePagination);
eventHub.$off('updateGroups', this.updateGroups);
},
};
</script>
......
......@@ -20,7 +20,11 @@ export default {
return this.parentGroup.childrenCount > MAX_CHILDREN_COUNT;
},
moreChildrenStats() {
return n__('One more item', '%d more items', this.parentGroup.childrenCount - this.parentGroup.children.length);
return n__(
'One more item',
'%d more items',
this.parentGroup.childrenCount - this.parentGroup.children.length,
);
},
},
};
......@@ -43,8 +47,9 @@ export default {
<i
class="fa fa-external-link"
aria-hidden="true"
/>
{{moreChildrenStats}}
>
</i>
{{ moreChildrenStats }}
</a>
</li>
</ul>
......
......@@ -75,7 +75,7 @@ export default {
:id="groupDomId"
:class="rowClass"
class="group-row"
>
>
<div
class="group-row-contents"
:class="{ 'project-row-contents': !isGroup }">
......@@ -88,7 +88,8 @@ export default {
:item="group"
/>
<div
class="folder-toggle-wrap">
class="folder-toggle-wrap"
>
<item-caret
:is-group-open="group.isOpen"
/>
......@@ -113,13 +114,14 @@ export default {
<identicon
v-else
size-class="s24"
:entity-id=group.id
:entity-id="group.id"
:entity-name="group.name"
/>
</a>
</div>
<div
class="title namespace-title">
class="title namespace-title"
>
<a
v-tooltip
:href="group.relativePath"
......@@ -135,7 +137,7 @@ export default {
v-if="group.permission"
class="user-access-role"
>
{{group.permission}}
{{ group.permission }}
</span>
</div>
<div
......
<script>
import tablePagination from '~/vue_shared/components/table_pagination.vue';
import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import tablePagination from '~/vue_shared/components/table_pagination.vue';
import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
export default {
components: {
tablePagination,
},
props: {
groups: {
type: Array,
required: true,
export default {
components: {
tablePagination,
},
pageInfo: {
type: Object,
required: true,
props: {
groups: {
type: Array,
required: true,
},
pageInfo: {
type: Object,
required: true,
},
searchEmpty: {
type: Boolean,
required: true,
},
searchEmptyMessage: {
type: String,
required: true,
},
},
searchEmpty: {
type: Boolean,
required: true,
methods: {
change(page) {
const filterGroupsParam = getParameterByName('filter_groups');
const sortParam = getParameterByName('sort');
const archivedParam = getParameterByName('archived');
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
},
},
searchEmptyMessage: {
type: String,
required: true,
},
},
methods: {
change(page) {
const filterGroupsParam = getParameterByName('filter_groups');
const sortParam = getParameterByName('sort');
const archivedParam = getParameterByName('archived');
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
},
},
};
};
</script>
<template>
<div class="groups-list-tree-container">
<div
v-if="searchEmpty"
class="has-no-search-results">
{{searchEmptyMessage}}
class="has-no-search-results"
>
{{ searchEmptyMessage }}
</div>
<group-folder
v-if="!searchEmpty"
......@@ -50,7 +51,7 @@ export default {
<table-pagination
v-if="!searchEmpty"
:change="change"
:pageInfo="pageInfo"
:page-info="pageInfo"
/>
</div>
</template>
<script>
import { s__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
import { s__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
export default {
components: {
icon,
modal,
},
directives: {
tooltip,
},
props: {
parentGroup: {
type: Object,
required: false,
default: () => ({}),
export default {
components: {
icon,
modal,
},
group: {
type: Object,
required: true,
directives: {
tooltip,
},
},
data() {
return {
modalStatus: false,
};
},
computed: {
leaveBtnTitle() {
return COMMON_STR.LEAVE_BTN_TITLE;
props: {
parentGroup: {
type: Object,
required: false,
default: () => ({}),
},
group: {
type: Object,
required: true,
},
},
editBtnTitle() {
return COMMON_STR.EDIT_BTN_TITLE;
data() {
return {
modalStatus: false,
};
},
leaveConfirmationMessage() {
return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
computed: {
leaveBtnTitle() {
return COMMON_STR.LEAVE_BTN_TITLE;
},
editBtnTitle() {
return COMMON_STR.EDIT_BTN_TITLE;
},
leaveConfirmationMessage() {
return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
},
},
},
methods: {
onLeaveGroup() {
this.modalStatus = true;
methods: {
onLeaveGroup() {
this.modalStatus = true;
},
leaveGroup() {
this.modalStatus = false;
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
},
},
leaveGroup() {
this.modalStatus = false;
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
},
},
};
};
</script>
<template>
......
......@@ -2,6 +2,9 @@
import icon from '~/vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
isGroupOpen: {
type: Boolean,
......@@ -9,9 +12,6 @@ export default {
default: false,
},
},
components: {
icon,
},
computed: {
iconClass() {
return this.isGroupOpen ? 'angle-down' : 'angle-right';
......
<script>
import icon from '~/vue_shared/components/icon.vue';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
import itemStatsValue from './item_stats_value.vue';
import icon from '~/vue_shared/components/icon.vue';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import {
ITEM_TYPE,
VISIBILITY_TYPE_ICON,
GROUP_VISIBILITY_TYPE,
PROJECT_VISIBILITY_TYPE,
} from '../constants';
import itemStatsValue from './item_stats_value.vue';
export default {
components: {
icon,
timeAgoTooltip,
itemStatsValue,
},
props: {
item: {
type: Object,
required: true,
export default {
components: {
icon,
timeAgoTooltip,
itemStatsValue,
},
},
computed: {
visibilityIcon() {
return VISIBILITY_TYPE_ICON[this.item.visibility];
props: {
item: {
type: Object,
required: true,
},
},
visibilityTooltip() {
if (this.item.type === ITEM_TYPE.GROUP) {
return GROUP_VISIBILITY_TYPE[this.item.visibility];
}
return PROJECT_VISIBILITY_TYPE[this.item.visibility];
computed: {
visibilityIcon() {
return VISIBILITY_TYPE_ICON[this.item.visibility];
},
visibilityTooltip() {
if (this.item.type === ITEM_TYPE.GROUP) {
return GROUP_VISIBILITY_TYPE[this.item.visibility];
}
return PROJECT_VISIBILITY_TYPE[this.item.visibility];
},
isProject() {
return this.item.type === ITEM_TYPE.PROJECT;
},
isGroup() {
return this.item.type === ITEM_TYPE.GROUP;
},
},
isProject() {
return this.item.type === ITEM_TYPE.PROJECT;
},
isGroup() {
return this.item.type === ITEM_TYPE.GROUP;
},
},
};
};
</script>
<template>
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
export default {
props: {
title: {
type: String,
required: false,
default: '',
export default {
components: {
icon,
},
cssClass: {
type: String,
required: false,
default: '',
directives: {
tooltip,
},
iconName: {
type: String,
required: true,
props: {
title: {
type: String,
required: false,
default: '',
},
cssClass: {
type: String,
required: false,
default: '',
},
iconName: {
type: String,
required: true,
},
tooltipPlacement: {
type: String,
required: false,
default: 'bottom',
},
/**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value: {
type: [Number, String],
required: false,
default: '',
},
},
tooltipPlacement: {
type: String,
required: false,
default: 'bottom',
computed: {
isValuePresent() {
return this.value !== '';
},
},
/**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value: {
type: [Number, String],
required: false,
default: '',
},
},
directives: {
tooltip,
},
components: {
icon,
},
computed: {
isValuePresent() {
return this.value !== '';
},
},
};
};
</script>
<template>
......@@ -57,12 +57,12 @@ export default {
:class="cssClass"
:title="title"
>
<icon :name="iconName"/>
<icon :name="iconName" />
<span
v-if="isValuePresent"
class="stat-value"
>
{{value}}
{{ value }}
</span>
</span>
</template>
......@@ -32,7 +32,6 @@
this.$emit('toggleCollapsed');
},
},
};
</script>
......
<script>
import { mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue';
import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue';
import repoPreview from './repo_preview.vue';
import repoEditor from './repo_editor.vue';
import { mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue';
import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue';
import repoPreview from './repo_preview.vue';
import repoEditor from './repo_editor.vue';
export default {
props: {
emptyStateSvgPath: {
type: String,
required: true,
export default {
components: {
ideSidebar,
ideContextbar,
repoTabs,
repoFileButtons,
ideStatusBar,
repoEditor,
repoPreview,
},
},
computed: {
...mapState([
'currentBlobView',
'selectedFile',
]),
...mapGetters([
'changedFiles',
'activeFile',
]),
},
components: {
ideSidebar,
ideContextbar,
repoTabs,
repoFileButtons,
ideStatusBar,
repoEditor,
repoPreview,
},
mounted() {
const returnValue = 'Are you sure you want to lose unsaved changes?';
window.onbeforeunload = (e) => {
if (!this.changedFiles.length) return undefined;
props: {
emptyStateSvgPath: {
type: String,
required: true,
},
},
computed: {
...mapState([
'currentBlobView',
'selectedFile',
]),
...mapGetters([
'changedFiles',
'activeFile',
]),
},
mounted() {
const returnValue = 'Are you sure you want to lose unsaved changes?';
window.onbeforeunload = (e) => {
if (!this.changedFiles.length) return undefined;
Object.assign(e, {
returnValue,
});
return returnValue;
};
},
};
Object.assign(e, {
returnValue,
});
return returnValue;
};
},
};
</script>
<template>
<div
<div
class="ide-view"
>
<ide-sidebar/>
<ide-sidebar />
<div
class="multi-file-edit-pane"
>
<template
v-if="activeFile">
v-if="activeFile"
>
<repo-tabs/>
<component
class="multi-file-edit-pane-content"
:is="currentBlobView"
/>
<repo-file-buttons/>
<repo-file-buttons />
<ide-status-bar
:file="selectedFile"/>
:file="selectedFile"
/>
</template>
<template
v-else>
v-else
>
<div class="ide-empty-state">
<div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
<img :src="emptyStateSvgPath">
<img :src="emptyStateSvgPath" />
</div>
</div>
<div class="col-xs-12">
......@@ -82,7 +85,8 @@ export default {
Welcome to the GitLab IDE
</h4>
<p>
You can select a file in the left sidebar to begin editing and use the right sidebar to commit your changes.
You can select a file in the left sidebar to begin
editing and use the right sidebar to commit your changes.
</p>
</div>
</div>
......
<script>
import { mapGetters, mapState, mapActions } from 'vuex';
import repoCommitSection from './repo_commit_section.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
import { mapGetters, mapState, mapActions } from 'vuex';
import repoCommitSection from './repo_commit_section.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
export default {
data() {
return {
width: 290,
};
},
components: {
repoCommitSection,
icon,
panelResizer,
},
computed: {
...mapState([
'rightPanelCollapsed',
]),
...mapGetters([
'changedFiles',
]),
currentIcon() {
return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
export default {
components: {
repoCommitSection,
icon,
panelResizer,
},
maxSize() {
return window.innerWidth / 2;
data() {
return {
width: 290,
};
},
panelStyle() {
if (!this.rightPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
computed: {
...mapState([
'rightPanelCollapsed',
]),
...mapGetters([
'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,
});
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
]),
toggleCollapsed() {
this.setPanelCollapsedStatus({
side: 'right',
collapsed: !this.rightPanelCollapsed,
});
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
},
};
};
</script>
<template>
......@@ -64,17 +64,17 @@ export default {
}"
:style="panelStyle"
>
<div
class="multi-file-commit-panel-section">
<div class="multi-file-commit-panel-section">
<header
class="multi-file-commit-panel-header"
:class="{
'is-collapsed': rightPanelCollapsed,
}"
>
'is-collapsed': rightPanelCollapsed,
}"
>
<div
class="multi-file-commit-panel-header-title"
v-if="!rightPanelCollapsed">
v-if="!rightPanelCollapsed"
>
<icon
name="list-bulleted"
:size="18"
......@@ -92,8 +92,7 @@ export default {
/>
</button>
</header>
<repo-commit-section
class=""/>
<repo-commit-section />
</div>
<panel-resizer
:size.sync="width"
......@@ -103,6 +102,7 @@ export default {
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="left"/>
side="left"
/>
</div>
</template>
......@@ -28,20 +28,20 @@ export default {
<div class="branch-header-title">
<icon
name="branch"
:size="12">
</icon>
:size="12"
/>
{{ branch.name }}
</div>
<div class="branch-header-btns">
<new-dropdown
:project-id="projectId"
:branch="branch.name"
path=""/>
path=""
/>
</div>
</div>
<div>
<repo-tree
:treeId="branch.treeId"/>
<repo-tree :tree-id="branch.treeId" />
</div>
</div>
</template>
......@@ -19,9 +19,10 @@ export default {
<template>
<div class="projects-sidebar">
<div class="context-header">
<a
:title="project.name"
:href="project.web_url">
<a
:title="project.name"
:href="project.web_url"
>
<div class="avatar-container s40 project-avatar">
<project-avatar-image
class="avatar-container project-avatar"
......@@ -29,7 +30,7 @@ export default {
:img-src="project.avatar_url"
:img-alt="project.name"
:img-size="40"
/>
/>
</div>
<div class="sidebar-context-title">
{{ project.name }}
......@@ -38,10 +39,11 @@ export default {
</div>
<div class="multi-file-commit-panel-inner-scroll">
<branches-tree
v-for="(branch, index) in project.branches"
v-for="branch in project.branches"
:key="branch.name"
:project-id="project.path_with_namespace"
:branch="branch"/>
:branch="branch"
/>
</div>
</div>
</template>
......@@ -44,28 +44,31 @@ export default {
</script>
<template>
<div>
<div class="ide-file-list">
<table class="table">
<tbody
v-if="treeId">
<repo-previous-directory
v-if="hasPreviousDirectory"
/>
<div
class="multi-file-loading-container"
v-if="showLoading"
v-for="n in 3"
:key="n">
<skeleton-loading-container/>
</div>
<repo-file
v-for="file in fetchedList"
:key="file.key"
:file="file"
/>
</tbody>
</table>
<div>
<div class="ide-file-list">
<table class="table">
<tbody
v-if="treeId"
>
<repo-previous-directory
v-if="hasPreviousDirectory"
/>
<template v-if="showLoading">
<div
class="multi-file-loading-container"
v-for="n in 3"
:key="n"
>
<skeleton-loading-container />
</div>
</template>
<repo-file
v-for="file in fetchedList"
:key="file.key"
:file="file"
/>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import projectTree from './ide_project_tree.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
import { mapState, mapActions } from 'vuex';
import projectTree from './ide_project_tree.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
export default {
data() {
return {
width: 290,
};
},
components: {
projectTree,
icon,
panelResizer,
skeletonLoadingContainer,
},
computed: {
...mapState([
'loading',
'projects',
'leftPanelCollapsed',
]),
currentIcon() {
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
export default {
components: {
projectTree,
icon,
panelResizer,
skeletonLoadingContainer,
},
maxSize() {
return window.innerWidth / 2;
data() {
return {
width: 290,
};
},
panelStyle() {
if (!this.leftPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
computed: {
...mapState([
'loading',
'projects',
'leftPanelCollapsed',
]),
currentIcon() {
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
},
maxSize() {
return window.innerWidth / 2;
},
panelStyle() {
if (!this.leftPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
},
showLoading() {
return this.loading;
},
},
showLoading() {
return this.loading;
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
]),
toggleCollapsed() {
this.setPanelCollapsedStatus({
side: 'left',
collapsed: !this.leftPanelCollapsed,
});
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
},
},
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
]),
toggleCollapsed() {
this.setPanelCollapsedStatus({
side: 'left',
collapsed: !this.leftPanelCollapsed,
});
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
},
};
};
</script>
<template>
<div
class="multi-file-commit-panel"
:class="{
'is-collapsed': leftPanelCollapsed,
}"
:style="panelStyle"
>
class="multi-file-commit-panel"
:class="{
'is-collapsed': leftPanelCollapsed,
}"
:style="panelStyle"
>
<div class="multi-file-commit-panel-inner">
<div
class="multi-file-loading-container"
v-if="showLoading"
v-for="n in 3"
:key="n">
<skeleton-loading-container/>
</div>
<template v-if="showLoading">
<div
class="multi-file-loading-container"
v-for="n in 3"
:key="n"
>
<skeleton-loading-container />
</div>
</template>
<project-tree
v-for="(project, index) in projects"
v-for="project in projects"
:key="project.id"
:project="project"/>
:project="project"
/>
</div>
<button
type="button"
......@@ -93,7 +96,9 @@ export default {
<span
v-if="!leftPanelCollapsed"
class="collapse-text"
>Collapse sidebar</span>
>
Collapse sidebar
</span>
</button>
<panel-resizer
:size.sync="width"
......@@ -103,6 +108,7 @@ export default {
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="right"/>
side="right"
/>
</div>
</template>
<script>
import { mapState } from 'vuex';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
import { mapState } from 'vuex';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
export default {
props: {
file: {
type: Object,
required: true,
export default {
components: {
icon,
},
},
components: {
icon,
},
directives: {
tooltip,
},
mixins: [
timeAgoMixin,
],
computed: {
...mapState([
'selectedFile',
]),
},
};
directives: {
tooltip,
},
mixins: [
timeAgoMixin,
],
props: {
file: {
type: Object,
required: true,
},
},
computed: {
...mapState([
'selectedFile',
]),
},
};
</script>
<template>
<div
class="ide-status-bar">
<div class="ide-status-bar">
<div>
<icon
name="branch"
:size="12">
</icon>
:size="12"
/>
{{ selectedFile.branchId }}
</div>
<div>
<div
v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
<div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
Last commit:
<a
v-tooltip
:title="selectedFile.lastCommit.message"
:href="selectedFile.lastCommit.url">
{{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
:href="selectedFile.lastCommit.url"
>
{{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
{{ selectedFile.lastCommit.author }}
</a>
</div>
</div>
</div>
<div
class="text-right">
<div class="text-right">
{{ selectedFile.name }}
</div>
<div
class="text-right">
<div class="text-right">
{{ selectedFile.eol }}
</div>
<div
class="text-right">
<div class="text-right">
{{ file.editorRow }}:{{ file.editorColumn }}
</div>
<div
class="text-right">
<div class="text-right">
{{ selectedFile.fileLanguage }}
</div>
</div>
......
......@@ -21,6 +21,13 @@
return this.loading || this.branchName === '';
},
},
created() {
// Dropdown is outside of Vue instance & is controlled by Bootstrap
this.$dropdown = $('.git-revision-dropdown');
// text element is outside Vue app
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
},
methods: {
...mapActions([
'createNewBranch',
......@@ -55,13 +62,6 @@
}));
},
},
created() {
// Dropdown is outside of Vue instance & is controlled by Bootstrap
this.$dropdown = $('.git-revision-dropdown');
// text element is outside Vue app
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
},
};
</script>
......
......@@ -4,6 +4,11 @@
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
newModal,
upload,
},
props: {
branch: {
type: String,
......@@ -18,11 +23,6 @@
default: null,
},
},
components: {
icon,
newModal,
upload,
},
data() {
return {
openModal: false,
......
......@@ -4,6 +4,9 @@
import modal from '../../../vue_shared/components/modal.vue';
export default {
components: {
modal,
},
props: {
branchId: {
type: String,
......@@ -27,28 +30,6 @@
entryName: this.path !== '' ? `${this.path}/` : '',
};
},
components: {
modal,
},
methods: {
...mapActions([
'createTempEntry',
]),
createEntryInStore() {
this.createTempEntry({
projectId: this.currentProjectId,
branchId: this.branchId,
parent: this.parent,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type,
});
this.hideModal();
},
hideModal() {
this.$emit('hide');
},
},
computed: {
...mapState([
'currentProjectId',
......@@ -78,6 +59,25 @@
mounted() {
this.$refs.fieldName.focus();
},
methods: {
...mapActions([
'createTempEntry',
]),
createEntryInStore() {
this.createTempEntry({
projectId: this.currentProjectId,
branchId: this.branchId,
parent: this.parent,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type,
});
this.hideModal();
},
hideModal() {
this.$emit('hide');
},
},
};
</script>
......
......@@ -18,6 +18,12 @@
'currentProjectId',
]),
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
methods: {
...mapActions([
'createTempEntry',
......@@ -59,12 +65,6 @@
this.$refs.fileUpload.click();
},
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
};
</script>
......
......@@ -49,7 +49,9 @@ export default {
const createNewBranch = newBranch || this.startNewMR;
const payload = {
branch: createNewBranch ? `${this.currentBranchId}-${new Date().getTime().toString()}` : this.currentBranchId,
branch: createNewBranch ?
`${this.currentBranchId}-${new Date().getTime().toString()}` :
this.currentBranchId,
commit_message: this.commitMessage,
actions: this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
......@@ -103,69 +105,70 @@ export default {
</script>
<template>
<div class="multi-file-commit-panel-section">
<modal
v-if="showNewBranchModal"
:primary-button-label="__('Create new branch')"
kind="primary"
:title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
@cancel="showNewBranchModal = false"
@submit="makeCommit(true)"
/>
<commit-files-list
title="Staged"
:file-list="changedFiles"
:collapsed="rightPanelCollapsed"
@toggleCollapsed="toggleCollapsed"
/>
<form
class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit"
v-if="!rightPanelCollapsed"
>
<div class="multi-file-commit-fieldset">
<textarea
class="form-control multi-file-commit-message"
name="commit-message"
v-model="commitMessage"
placeholder="Commit message"
>
</textarea>
</div>
<div class="multi-file-commit-fieldset">
<label
v-tooltip
title="Create a new merge request with these changes"
data-container="body"
data-placement="top"
>
<input
type="checkbox"
v-model="startNewMR"
/>
Merge Request
</label>
<button
type="submit"
:disabled="commitButtonDisabled"
class="btn btn-default btn-sm append-right-10 prepend-left-10"
>
<i
v-if="submitCommitsLoading"
class="js-commit-loading-icon fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="loading"
<div class="multi-file-commit-panel-section">
<modal
v-if="showNewBranchModal"
:primary-button-label="__('Create new branch')"
kind="primary"
:title="__('Branch has changed')"
:text="__(`This branch has changed since
you started editing. Would you like to create a new branch?`)"
@cancel="showNewBranchModal = false"
@submit="makeCommit(true)"
/>
<commit-files-list
title="Staged"
:file-list="changedFiles"
:collapsed="rightPanelCollapsed"
@toggleCollapsed="toggleCollapsed"
/>
<form
class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit"
v-if="!rightPanelCollapsed"
>
<div class="multi-file-commit-fieldset">
<textarea
class="form-control multi-file-commit-message"
name="commit-message"
v-model="commitMessage"
placeholder="Commit message"
>
</i>
Commit
</button>
<div
class="multi-file-commit-message-count"
>
{{ commitMessageCount }}
</textarea>
</div>
</div>
</form>
</div>
<div class="multi-file-commit-fieldset">
<label
v-tooltip
title="Create a new merge request with these changes"
data-container="body"
data-placement="top"
>
<input
type="checkbox"
v-model="startNewMR"
/>
Merge Request
</label>
<button
type="submit"
:disabled="commitButtonDisabled"
class="btn btn-default btn-sm append-right-10 prepend-left-10"
>
<i
v-if="submitCommitsLoading"
class="js-commit-loading-icon fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="loading"
>
</i>
Commit
</button>
<div
class="multi-file-commit-message-count"
>
{{ commitMessageCount }}
</div>
</div>
</form>
</div>
</template>
......@@ -40,7 +40,7 @@ export default {
aria-hidden="true">
</i>
<span>
{{buttonLabel}}
{{ buttonLabel }}
</span>
</button>
<modal
......
......@@ -6,14 +6,14 @@
import fileIcon from '../../vue_shared/components/file_icon.vue';
export default {
mixins: [
timeAgoMixin,
],
components: {
skeletonLoadingContainer,
newDropdown,
fileIcon,
},
mixins: [
timeAgoMixin,
],
props: {
file: {
type: Object,
......@@ -60,6 +60,11 @@
};
},
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView();
}
},
methods: {
clickFile(row) {
// Manual Action if a tree is selected/opened
......@@ -72,11 +77,6 @@
this.$router.push(`/project${row.url}`);
},
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView();
}
},
};
</script>
......@@ -99,8 +99,7 @@
:opened="file.opened"
:style="levelIndentation"
:size="16"
>
</file-icon>
/>
{{ file.name }}
</a>
<new-dropdown
......@@ -108,7 +107,8 @@
:project-id="file.projectId"
:branch="file.branchId"
:path="file.path"
:parent="file"/>
:parent="file"
/>
<i
class="fa"
v-if="changedClass"
......
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册