diff options
Diffstat (limited to 'app/assets')
9 files changed, 187 insertions, 106 deletions
diff --git a/app/assets/javascripts/blob/components/blob_header.vue b/app/assets/javascripts/blob/components/blob_header.vue index 61a66513838..b7d9600ec40 100644 --- a/app/assets/javascripts/blob/components/blob_header.vue +++ b/app/assets/javascripts/blob/components/blob_header.vue @@ -2,8 +2,7 @@ import ViewerSwitcher from './blob_header_viewer_switcher.vue'; import DefaultActions from './blob_header_default_actions.vue'; import BlobFilepath from './blob_header_filepath.vue'; -import eventHub from '../event_hub'; -import { RICH_BLOB_VIEWER, SIMPLE_BLOB_VIEWER } from './constants'; +import { SIMPLE_BLOB_VIEWER } from './constants'; export default { components: { @@ -26,10 +25,15 @@ export default { required: false, default: false, }, + activeViewerType: { + type: String, + required: false, + default: SIMPLE_BLOB_VIEWER, + }, }, data() { return { - activeViewer: this.blob.richViewer ? RICH_BLOB_VIEWER : SIMPLE_BLOB_VIEWER, + viewer: this.hideViewerSwitcher ? null : this.activeViewerType, }; }, computed: { @@ -40,19 +44,16 @@ export default { return !this.hideDefaultActions; }, }, - created() { - if (this.showViewerSwitcher) { - eventHub.$on('switch-viewer', this.setActiveViewer); - } - }, - beforeDestroy() { - if (this.showViewerSwitcher) { - eventHub.$off('switch-viewer', this.setActiveViewer); - } + watch: { + viewer(newVal, oldVal) { + if (!this.hideViewerSwitcher && newVal !== oldVal) { + this.$emit('viewer-changed', newVal); + } + }, }, methods: { - setActiveViewer(viewer) { - this.activeViewer = viewer; + proxyCopyRequest() { + this.$emit('copy'); }, }, }; @@ -66,11 +67,16 @@ export default { </blob-filepath> <div class="file-actions d-none d-sm-block"> - <viewer-switcher v-if="showViewerSwitcher" :blob="blob" :active-viewer="activeViewer" /> + <viewer-switcher v-if="showViewerSwitcher" v-model="viewer" /> <slot name="actions"></slot> - <default-actions v-if="showDefaultActions" :blob="blob" :active-viewer="activeViewer" /> + <default-actions + v-if="showDefaultActions" + :raw-path="blob.rawPath" + :active-viewer="viewer" + @copy="proxyCopyRequest" + /> </div> </div> </template> diff --git a/app/assets/javascripts/blob/components/blob_header_default_actions.vue b/app/assets/javascripts/blob/components/blob_header_default_actions.vue index e526fae0dba..f5157fba819 100644 --- a/app/assets/javascripts/blob/components/blob_header_default_actions.vue +++ b/app/assets/javascripts/blob/components/blob_header_default_actions.vue @@ -7,7 +7,6 @@ import { RICH_BLOB_VIEWER, SIMPLE_BLOB_VIEWER, } from './constants'; -import eventHub from '../event_hub'; export default { components: { @@ -19,8 +18,8 @@ export default { GlTooltip: GlTooltipDirective, }, props: { - blob: { - type: Object, + rawPath: { + type: String, required: true, }, activeViewer: { @@ -30,11 +29,8 @@ export default { }, }, computed: { - rawUrl() { - return this.blob.rawPath; - }, downloadUrl() { - return `${this.blob.rawPath}?inline=false`; + return `${this.rawPath}?inline=false`; }, copyDisabled() { return this.activeViewer === RICH_BLOB_VIEWER; @@ -42,7 +38,7 @@ export default { }, methods: { requestCopyContents() { - eventHub.$emit('copy'); + this.$emit('copy'); }, }, BTN_COPY_CONTENTS_TITLE, @@ -65,7 +61,7 @@ export default { v-gl-tooltip.hover :aria-label="$options.BTN_RAW_TITLE" :title="$options.BTN_RAW_TITLE" - :href="rawUrl" + :href="rawPath" target="_blank" > <gl-icon name="doc-code" :size="14" /> diff --git a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue index 13ea87c99b1..689fa7638f0 100644 --- a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue +++ b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue @@ -6,7 +6,6 @@ import { SIMPLE_BLOB_VIEWER, SIMPLE_BLOB_VIEWER_TITLE, } from './constants'; -import eventHub from '../event_hub'; export default { components: { @@ -18,11 +17,7 @@ export default { GlTooltip: GlTooltipDirective, }, props: { - blob: { - type: Object, - required: true, - }, - activeViewer: { + value: { type: String, default: SIMPLE_BLOB_VIEWER, required: false, @@ -30,16 +25,16 @@ export default { }, computed: { isSimpleViewer() { - return this.activeViewer === SIMPLE_BLOB_VIEWER; + return this.value === SIMPLE_BLOB_VIEWER; }, isRichViewer() { - return this.activeViewer === RICH_BLOB_VIEWER; + return this.value === RICH_BLOB_VIEWER; }, }, methods: { switchToViewer(viewer) { - if (viewer !== this.activeViewer) { - eventHub.$emit('switch-viewer', viewer); + if (viewer !== this.value) { + this.$emit('input', viewer); } }, }, diff --git a/app/assets/javascripts/blob/event_hub.js b/app/assets/javascripts/blob/event_hub.js deleted file mode 100644 index 0948c2e5352..00000000000 --- a/app/assets/javascripts/blob/event_hub.js +++ /dev/null @@ -1,3 +0,0 @@ -import Vue from 'vue'; - -export default new Vue(); diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index 8c84b98a108..0fab3ee0f3b 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -1,11 +1,11 @@ import Vue from 'vue'; -import VueRouter from 'vue-router'; +import IdeRouter from '~/ide/ide_router_extension'; import { joinPaths } from '~/lib/utils/url_utility'; import flash from '~/flash'; import store from './stores'; import { __ } from '~/locale'; -Vue.use(VueRouter); +Vue.use(IdeRouter); /** * Routes below /-/ide/: @@ -33,7 +33,7 @@ const EmptyRouterComponent = { }, }; -const router = new VueRouter({ +const router = new IdeRouter({ mode: 'history', base: joinPaths(gon.relative_url_root || '', '/-/ide/'), routes: [ diff --git a/app/assets/javascripts/ide/ide_router_extension.js b/app/assets/javascripts/ide/ide_router_extension.js new file mode 100644 index 00000000000..a146aca7283 --- /dev/null +++ b/app/assets/javascripts/ide/ide_router_extension.js @@ -0,0 +1,21 @@ +import VueRouter from 'vue-router'; +import { escapeFileUrl } from '~/lib/utils/url_utility'; + +// To allow special characters (like "#," for example) in the branch names, we +// should encode all the locations before those get processed by History API. +// Otherwise, paths get messed up so that the router receives incorrect +// branchid. The only way to do it consistently and in a more or less +// future-proof manner is, unfortunately, to monkey-patch VueRouter or, as +// suggested here, achieve the same more reliably by subclassing VueRouter and +// update the methods, used in WebIDE. +// +// More context: https://gitlab.com/gitlab-org/gitlab/issues/35473 + +export default class IDERouter extends VueRouter { + push(location, onComplete, onAbort) { + super.push(escapeFileUrl(location), onComplete, onAbort); + } + resolve(to, current, append) { + return super.resolve(escapeFileUrl(to), current, append); + } +} diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 267b49e9d98..1ff4f7bab97 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -194,12 +194,14 @@ export function redirectTo(url) { return window.location.assign(url); } +export const escapeFileUrl = fileUrl => encodeURIComponent(fileUrl).replace(/%2F/g, '/'); + export function webIDEUrl(route = undefined) { let returnUrl = `${gon.relative_url_root || ''}/-/ide/`; if (route) { returnUrl += `project${route.replace(new RegExp(`^${gon.relative_url_root || ''}`), '')}`; } - return returnUrl; + return escapeFileUrl(returnUrl); } /** @@ -313,8 +315,6 @@ export const setUrlParams = (params, url = window.location.href, clearParams = f return urlObj.toString(); }; -export const escapeFileUrl = fileUrl => encodeURIComponent(fileUrl).replace(/%2F/g, '/'); - export function urlIsDifferent(url, compare = String(window.location)) { return url !== compare; } diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js index 1b94fb06107..803f4e37705 100644 --- a/app/assets/javascripts/pages/projects/graphs/charts/index.js +++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js @@ -1,47 +1,15 @@ -import $ from 'jquery'; -import Chart from 'chart.js'; -import { barChartOptions, pieChartOptions } from '~/lib/utils/chart_utils'; +import Vue from 'vue'; +import { __ } from '~/locale'; +import { GlColumnChart } from '@gitlab/ui/dist/charts'; +import SeriesDataMixin from './series_data_mixin'; document.addEventListener('DOMContentLoaded', () => { - const projectChartData = JSON.parse(document.getElementById('projectChartData').innerHTML); + const languagesContainer = document.getElementById('js-languages-chart'); + const monthContainer = document.getElementById('js-month-chart'); + const weekdayContainer = document.getElementById('js-weekday-chart'); + const hourContainer = document.getElementById('js-hour-chart'); - const barChart = (selector, data) => { - // get selector by context - const ctx = selector.get(0).getContext('2d'); - // pointing parent container to make chart.js inherit its width - const container = $(selector).parent(); - selector.attr('width', $(container).width()); - - // Scale fonts if window width lower than 768px (iPad portrait) - const shouldAdjustFontSize = window.innerWidth < 768; - return new Chart(ctx, { - type: 'bar', - data, - options: barChartOptions(shouldAdjustFontSize), - }); - }; - - const pieChart = (context, data) => { - const options = pieChartOptions(); - - return new Chart(context, { - type: 'pie', - data, - options, - }); - }; - - const chartData = data => ({ - labels: Object.keys(data), - datasets: [ - { - backgroundColor: 'rgba(220,220,220,0.5)', - borderColor: 'rgba(220,220,220,1)', - borderWidth: 1, - data: Object.values(data), - }, - ], - }); + const LANGUAGE_CHART_HEIGHT = 300; const reorderWeekDays = (weekDays, firstDayOfWeek = 0) => { if (firstDayOfWeek === 0) { @@ -58,28 +26,115 @@ document.addEventListener('DOMContentLoaded', () => { }, {}); }; - const hourData = chartData(projectChartData.hour); - barChart($('#hour-chart'), hourData); - - const weekDays = reorderWeekDays(projectChartData.weekDays, gon.first_day_of_week); - const dayData = chartData(weekDays); - barChart($('#weekday-chart'), dayData); + // eslint-disable-next-line no-new + new Vue({ + el: languagesContainer, + components: { + GlColumnChart, + }, + data() { + return { + chartData: JSON.parse(languagesContainer.dataset.chartData), + }; + }, + computed: { + seriesData() { + return { full: this.chartData.map(d => [d.label, d.value]) }; + }, + }, + render(h) { + return h(GlColumnChart, { + props: { + data: this.seriesData, + xAxisTitle: __('Used programming language'), + yAxisTitle: __('Percentage'), + xAxisType: 'category', + }, + attrs: { + height: LANGUAGE_CHART_HEIGHT, + }, + }); + }, + }); - const monthData = chartData(projectChartData.month); - barChart($('#month-chart'), monthData); + // eslint-disable-next-line no-new + new Vue({ + el: monthContainer, + components: { + GlColumnChart, + }, + mixins: [SeriesDataMixin], + data() { + return { + chartData: JSON.parse(monthContainer.dataset.chartData), + }; + }, + render(h) { + return h(GlColumnChart, { + props: { + data: this.seriesData, + xAxisTitle: __('Day of month'), + yAxisTitle: __('No. of commits'), + xAxisType: 'category', + }, + }); + }, + }); - const data = { - datasets: [ - { - data: projectChartData.languages.map(x => x.value), - backgroundColor: projectChartData.languages.map(x => x.color), - hoverBackgroundColor: projectChartData.languages.map(x => x.highlight), + // eslint-disable-next-line no-new + new Vue({ + el: weekdayContainer, + components: { + GlColumnChart, + }, + data() { + return { + chartData: JSON.parse(weekdayContainer.dataset.chartData), + }; + }, + computed: { + seriesData() { + const weekDays = reorderWeekDays(this.chartData, gon.first_day_of_week); + const data = Object.keys(weekDays).reduce((acc, key) => { + acc.push([key, weekDays[key]]); + return acc; + }, []); + return { full: data }; }, - ], - labels: projectChartData.languages.map(x => x.label), - }; - const ctx = $('#languages-chart') - .get(0) - .getContext('2d'); - pieChart(ctx, data); + }, + render(h) { + return h(GlColumnChart, { + props: { + data: this.seriesData, + xAxisTitle: __('Weekday'), + yAxisTitle: __('No. of commits'), + xAxisType: 'category', + }, + }); + }, + }); + + // eslint-disable-next-line no-new + new Vue({ + el: hourContainer, + components: { + GlColumnChart, + }, + mixins: [SeriesDataMixin], + data() { + return { + chartData: JSON.parse(hourContainer.dataset.chartData), + }; + }, + render(h) { + return h(GlColumnChart, { + props: { + data: this.seriesData, + xAxisTitle: __('Hour (UTC)'), + yAxisTitle: __('No. of commits'), + xAxisType: 'category', + }, + }); + }, + }); }); diff --git a/app/assets/javascripts/pages/projects/graphs/charts/series_data_mixin.js b/app/assets/javascripts/pages/projects/graphs/charts/series_data_mixin.js new file mode 100644 index 00000000000..941427a1ac3 --- /dev/null +++ b/app/assets/javascripts/pages/projects/graphs/charts/series_data_mixin.js @@ -0,0 +1,11 @@ +export default { + computed: { + seriesData() { + const data = Object.keys(this.chartData).reduce((acc, key) => { + acc.push([key, this.chartData[key]]); + return acc; + }, []); + return { full: data }; + }, + }, +}; |