diff options
Diffstat (limited to 'app/assets/javascripts/ide')
-rw-r--r-- | app/assets/javascripts/ide/components/panes/right.vue | 15 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/preview/clientside.vue | 191 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/preview/navigator.vue | 136 | ||||
-rw-r--r-- | app/assets/javascripts/ide/constants.js | 10 | ||||
-rw-r--r-- | app/assets/javascripts/ide/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/ide/init_gitlab_web_ide.js | 10 | ||||
-rw-r--r-- | app/assets/javascripts/ide/lib/editor_options.js | 9 | ||||
-rw-r--r-- | app/assets/javascripts/ide/lib/gitlab_web_ide/handle_tracking_event.js | 20 | ||||
-rw-r--r-- | app/assets/javascripts/ide/lib/mirror.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/ide/remote/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/actions.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/getters.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/modules/clientside/actions.js | 11 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/modules/clientside/index.js | 6 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/state.js | 2 |
16 files changed, 39 insertions, 386 deletions
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue index da2d4fbe7f0..8342b3f428c 100644 --- a/app/assets/javascripts/ide/components/panes/right.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -1,10 +1,9 @@ <script> -import { mapGetters, mapState } from 'vuex'; +import { mapState } from 'vuex'; import { __ } from '~/locale'; import { rightSidebarViews, SIDEBAR_INIT_WIDTH, SIDEBAR_NAV_WIDTH } from '../../constants'; import JobsDetail from '../jobs/detail.vue'; import PipelinesList from '../pipelines/list.vue'; -import Clientside from '../preview/clientside.vue'; import ResizablePanel from '../resizable_panel.vue'; import TerminalView from '../terminal/view.vue'; import CollapsibleSidebar from './collapsible_sidebar.vue'; @@ -20,12 +19,8 @@ export default { }, computed: { ...mapState('terminal', { isTerminalVisible: 'isVisible' }), - ...mapState(['currentMergeRequestId', 'clientsidePreviewEnabled']), - ...mapGetters(['packageJson']), + ...mapState(['currentMergeRequestId']), ...mapState('rightPane', ['isOpen']), - showLivePreview() { - return this.packageJson && this.clientsidePreviewEnabled; - }, rightExtensionTabs() { return [ { @@ -38,12 +33,6 @@ export default { icon: 'rocket', }, { - show: this.showLivePreview, - title: __('Live preview'), - views: [{ component: Clientside, ...rightSidebarViews.clientSidePreview }], - icon: 'live-preview', - }, - { show: this.isTerminalVisible, title: __('Terminal'), views: [{ component: TerminalView, ...rightSidebarViews.terminal }], diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue deleted file mode 100644 index 70b881b6ff6..00000000000 --- a/app/assets/javascripts/ide/components/preview/clientside.vue +++ /dev/null @@ -1,191 +0,0 @@ -<script> -import { GlLoadingIcon } from '@gitlab/ui'; -import { listen } from 'codesandbox-api'; -import { isEmpty, debounce } from 'lodash'; -import { SandpackClient } from '@codesandbox/sandpack-client'; -import { mapActions, mapGetters, mapState } from 'vuex'; -import { - packageJsonPath, - LIVE_PREVIEW_DEBOUNCE, - PING_USAGE_PREVIEW_KEY, - PING_USAGE_PREVIEW_SUCCESS_KEY, -} from '../../constants'; -import eventHub from '../../eventhub'; -import { createPathWithExt } from '../../utils'; -import Navigator from './navigator.vue'; - -export default { - components: { - Navigator, - GlLoadingIcon, - }, - data() { - return { - client: {}, - loading: false, - sandpackReady: false, - }; - }, - computed: { - ...mapState(['entries', 'promotionSvgPath', 'links', 'codesandboxBundlerUrl']), - ...mapGetters(['packageJson', 'currentProject']), - normalizedEntries() { - return Object.keys(this.entries).reduce((acc, path) => { - const file = this.entries[path]; - - if (file.type === 'tree' || !(file.raw || file.content)) return acc; - - return { - ...acc, - [`/${path}`]: { - code: file.content || file.raw, - }, - }; - }, {}); - }, - mainEntry() { - if (!this.packageJson.raw) return false; - - const parsedPackage = JSON.parse(this.packageJson.raw); - - return parsedPackage.main; - }, - showPreview() { - return this.mainEntry && !this.loading; - }, - showEmptyState() { - return !this.mainEntry && !this.loading; - }, - showOpenInCodeSandbox() { - return this.currentProject && this.currentProject.visibility === 'public'; - }, - sandboxOpts() { - return { - files: { ...this.normalizedEntries }, - entry: `/${this.mainEntry}`, - showOpenInCodeSandbox: this.showOpenInCodeSandbox, - }; - }, - }, - watch: { - sandpackReady: { - handler(val) { - if (val) { - this.pingUsage(PING_USAGE_PREVIEW_SUCCESS_KEY); - } - }, - }, - }, - mounted() { - this.onFilesChangeCallback = debounce(() => this.update(), LIVE_PREVIEW_DEBOUNCE); - eventHub.$on('ide.files.change', this.onFilesChangeCallback); - - this.loading = true; - - return this.loadFileContent(packageJsonPath) - .then(() => { - this.loading = false; - }) - .then(() => this.$nextTick()) - .then(() => this.initPreview()); - }, - beforeDestroy() { - // Setting sandpackReady = false protects us form a phantom `update()` being called when `debounce` finishes. - this.sandpackReady = false; - eventHub.$off('ide.files.change', this.onFilesChangeCallback); - - if (!isEmpty(this.client)) { - this.client.cleanup(); - } - - this.client = {}; - - if (this.listener) { - this.listener(); - } - }, - methods: { - ...mapActions(['getFileData', 'getRawFileData']), - ...mapActions('clientside', ['pingUsage']), - loadFileContent(path) { - return this.getFileData({ path, makeFileActive: false }).then(() => - this.getRawFileData({ path }), - ); - }, - initPreview() { - if (!this.mainEntry) return null; - - this.pingUsage(PING_USAGE_PREVIEW_KEY); - - return this.loadFileContent(this.mainEntry) - .then(() => this.$nextTick()) - .then(() => { - this.initClient(); - - this.listener = listen((e) => { - switch (e.type) { - case 'done': - this.sandpackReady = true; - break; - default: - break; - } - }); - }); - }, - update() { - if (!this.sandpackReady) return; - - if (isEmpty(this.client)) { - this.initPreview(); - - return; - } - - this.client.updatePreview(this.sandboxOpts); - }, - initClient() { - const { codesandboxBundlerUrl: bundlerURL } = this; - - const settings = { - fileResolver: { - isFile: (p) => Promise.resolve(Boolean(this.entries[createPathWithExt(p)])), - readFile: (p) => this.loadFileContent(createPathWithExt(p)).then((content) => content), - }, - ...(bundlerURL ? { bundlerURL } : {}), - }; - - this.client = new SandpackClient('#ide-preview', this.sandboxOpts, settings); - }, - }, -}; -</script> - -<template> - <div class="preview h-100 w-100 d-flex flex-column gl-bg-white"> - <template v-if="showPreview"> - <navigator :client="client" /> - <div id="ide-preview"></div> - </template> - <div - v-else-if="showEmptyState" - v-once - class="d-flex h-100 flex-column align-items-center justify-content-center svg-content" - > - <img :src="promotionSvgPath" :alt="s__('IDE|Live Preview')" width="130" height="100" /> - <h3>{{ s__('IDE|Live Preview') }}</h3> - <p class="text-center"> - {{ s__('IDE|Preview your web application using Web IDE client-side evaluation.') }} - </p> - <a - :href="links.webIDEHelpPagePath" - class="btn gl-button btn-confirm" - target="_blank" - rel="noopener noreferrer" - > - {{ s__('IDE|Get started with Live Preview') }} - </a> - </div> - <gl-loading-icon v-else size="lg" class="align-self-center mt-auto mb-auto" /> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue deleted file mode 100644 index 852de16d508..00000000000 --- a/app/assets/javascripts/ide/components/preview/navigator.vue +++ /dev/null @@ -1,136 +0,0 @@ -<script> -import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; -import { listen } from 'codesandbox-api'; - -export default { - components: { - GlIcon, - GlLoadingIcon, - }, - props: { - client: { - type: Object, - required: true, - }, - }, - data() { - return { - currentBrowsingIndex: null, - navigationStack: [], - forwardNavigationStack: [], - path: '', - loading: true, - }; - }, - computed: { - backButtonDisabled() { - return this.navigationStack.length <= 1; - }, - forwardButtonDisabled() { - return !this.forwardNavigationStack.length; - }, - }, - mounted() { - this.listener = listen((e) => { - switch (e.type) { - case 'urlchange': - this.onUrlChange(e); - break; - case 'done': - this.loading = false; - break; - default: - break; - } - }); - }, - beforeDestroy() { - this.listener(); - }, - methods: { - onUrlChange(e) { - const lastPath = this.path; - - this.path = e.url.replace(this.client.bundlerURL, '') || '/'; - - if (lastPath !== this.path) { - this.currentBrowsingIndex = - this.currentBrowsingIndex === null ? 0 : this.currentBrowsingIndex + 1; - this.navigationStack.push(this.path); - } - }, - back() { - const lastPath = this.path; - - this.visitPath(this.navigationStack[this.currentBrowsingIndex - 1]); - - this.forwardNavigationStack.push(lastPath); - - if (this.currentBrowsingIndex === 1) { - this.currentBrowsingIndex = null; - this.navigationStack = []; - } - }, - forward() { - this.visitPath(this.forwardNavigationStack.splice(0, 1)[0]); - }, - refresh() { - this.visitPath(this.path); - }, - visitPath(path) { - // eslint-disable-next-line vue/no-mutating-props - this.client.iframe.src = `${this.client.bundlerURL}${path}`; - }, - }, -}; -</script> - -<template> - <header class="ide-preview-header d-flex align-items-center"> - <button - :aria-label="s__('IDE|Back')" - :disabled="backButtonDisabled" - :class="{ - 'disabled-content': backButtonDisabled, - }" - type="button" - class="ide-navigator-btn d-flex align-items-center d-transparent border-0 bg-transparent" - @click="back" - > - <gl-icon :size="24" name="chevron-left" class="m-auto" /> - </button> - <button - :aria-label="s__('IDE|Back')" - :disabled="forwardButtonDisabled" - :class="{ - 'disabled-content': forwardButtonDisabled, - }" - type="button" - class="ide-navigator-btn d-flex align-items-center d-transparent border-0 bg-transparent" - @click="forward" - > - <gl-icon :size="24" name="chevron-right" class="m-auto" /> - </button> - <button - :aria-label="s__('IDE|Refresh preview')" - type="button" - class="ide-navigator-btn d-flex align-items-center d-transparent border-0 bg-transparent" - @click="refresh" - > - <gl-icon :size="16" name="retry" class="m-auto" /> - </button> - <div class="position-relative w-100 gl-ml-2"> - <input - :value="path || '/'" - type="text" - class="ide-navigator-location form-control bg-white" - readonly - /> - <gl-loading-icon - v-if="loading" - size="sm" - class="position-absolute ide-preview-loading-icon" - /> - </div> - </header> -</template> diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 01ce5fa07ee..1aa64656c30 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -64,7 +64,6 @@ export const rightSidebarViews = { pipelines: { name: 'pipelines-list', keepAlive: true }, jobsDetail: { name: 'jobs-detail', keepAlive: false }, mergeRequestInfo: { name: 'merge-request-info', keepAlive: true }, - clientSidePreview: { name: 'clientside', keepAlive: false }, terminal: { name: 'terminal', keepAlive: true }, }; @@ -101,22 +100,13 @@ export const commitActionTypes = { update: 'update', }; -export const packageJsonPath = 'package.json'; - export const SIDE_LEFT = 'left'; export const SIDE_RIGHT = 'right'; -// Live Preview feature -export const LIVE_PREVIEW_DEBOUNCE = 2000; - // This is the maximum number of files to auto open when opening the Web IDE // from a merge request export const MAX_MR_FILES_AUTO_OPEN = 10; export const DEFAULT_BRANCH = 'main'; -// Ping Usage Metrics Keys -export const PING_USAGE_PREVIEW_KEY = 'web_ide_clientside_preview'; -export const PING_USAGE_PREVIEW_SUCCESS_KEY = 'web_ide_clientside_preview_success'; - export const GITLAB_WEB_IDE_FEEDBACK_ISSUE = 'https://gitlab.com/gitlab-org/gitlab/-/issues/377367'; diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 1347d92b3b7..29c44d2f596 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -67,10 +67,8 @@ export const initLegacyWebIDE = (el, options = {}) => { forkInfo: el.dataset.forkInfo ? JSON.parse(el.dataset.forkInfo) : null, }); this.init({ - clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled), renderWhitespaceInCode: parseBoolean(el.dataset.renderWhitespaceInCode), editorTheme: window.gon?.user_color_scheme || DEFAULT_THEME, - codesandboxBundlerUrl: el.dataset.codesandboxBundlerUrl, environmentsGuidanceAlertDismissed: !parseBoolean(el.dataset.enableEnvironmentsGuidance), previewMarkdownPath: el.dataset.previewMarkdownPath, userPreferencesPath: el.dataset.userPreferencesPath, diff --git a/app/assets/javascripts/ide/init_gitlab_web_ide.js b/app/assets/javascripts/ide/init_gitlab_web_ide.js index d3c64754e8a..4d3cefcb107 100644 --- a/app/assets/javascripts/ide/init_gitlab_web_ide.js +++ b/app/assets/javascripts/ide/init_gitlab_web_ide.js @@ -7,6 +7,7 @@ import csrf from '~/lib/utils/csrf'; import { getBaseConfig } from './lib/gitlab_web_ide/get_base_config'; import { setupRootElement } from './lib/gitlab_web_ide/setup_root_element'; import { GITLAB_WEB_IDE_FEEDBACK_ISSUE } from './constants'; +import { handleTracking } from './lib/gitlab_web_ide/handle_tracking_event'; const buildRemoteIdeURL = (ideRemotePath, remoteHost, remotePathArg) => { const remotePath = cleanLeadingSeparator(remotePathArg); @@ -38,6 +39,9 @@ export const initGitlabWebIDE = async (el) => { filePath, mergeRequest: mrId, forkInfo: forkInfoJSON, + editorFontSrcUrl, + editorFontFormat, + editorFontFamily, } = el.dataset; const rootEl = setupRootElement(el); @@ -64,6 +68,12 @@ export const initGitlabWebIDE = async (el) => { feedbackIssue: GITLAB_WEB_IDE_FEEDBACK_ISSUE, userPreferences: el.dataset.userPreferencesPath, }, + editorFont: { + srcUrl: editorFontSrcUrl, + fontFamily: editorFontFamily, + format: editorFontFormat, + }, + handleTracking, async handleStartRemote({ remoteHost, remotePath, connectionToken }) { const confirmed = await confirmAction( __('Are you sure you want to leave the Web IDE? All unsaved changes will be lost.'), diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js index 289027c3054..7a516f5e3f5 100644 --- a/app/assets/javascripts/ide/lib/editor_options.js +++ b/app/assets/javascripts/ide/lib/editor_options.js @@ -1,12 +1,5 @@ -import { useNewFonts } from '~/lib/utils/common_utils'; import { getCssVariable } from '~/lib/utils/css_utils'; -const fontOptions = {}; - -if (useNewFonts()) { - fontOptions.fontFamily = getCssVariable('--code-editor-font'); -} - export const defaultEditorOptions = { model: null, readOnly: false, @@ -18,7 +11,7 @@ export const defaultEditorOptions = { wordWrap: 'on', glyphMargin: true, automaticLayout: true, - ...fontOptions, + fontFamily: getCssVariable('--code-editor-font'), }; export const defaultDiffOptions = { diff --git a/app/assets/javascripts/ide/lib/gitlab_web_ide/handle_tracking_event.js b/app/assets/javascripts/ide/lib/gitlab_web_ide/handle_tracking_event.js new file mode 100644 index 00000000000..615dad02386 --- /dev/null +++ b/app/assets/javascripts/ide/lib/gitlab_web_ide/handle_tracking_event.js @@ -0,0 +1,20 @@ +import { snakeCase } from 'lodash'; +import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils'; +import Tracking from '~/tracking'; + +export const handleTracking = ({ name, data }) => { + const snakeCaseEventName = snakeCase(name); + + if (data && Object.keys(data).length) { + Tracking.event(undefined, snakeCaseEventName, { + /* See GitLab snowplow schema for a definition of the extra field + * https://docs.gitlab.com/ee/development/snowplow/schemas.html#gitlab_standard. + */ + extra: convertObjectPropsToSnakeCase(data, { + deep: true, + }), + }); + } else { + Tracking.event(undefined, snakeCaseEventName); + } +}; diff --git a/app/assets/javascripts/ide/lib/mirror.js b/app/assets/javascripts/ide/lib/mirror.js index 78990953beb..f437965b25a 100644 --- a/app/assets/javascripts/ide/lib/mirror.js +++ b/app/assets/javascripts/ide/lib/mirror.js @@ -1,3 +1,4 @@ +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { getWebSocketUrl, mergeUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import createDiff from './create_diff'; @@ -26,7 +27,7 @@ const cancellableWait = (time) => { const isErrorResponse = (error) => error && error.code !== 0; -const isErrorPayload = (payload) => payload && payload.status_code !== 200; +const isErrorPayload = (payload) => payload && payload.status_code !== HTTP_STATUS_OK; const getErrorFromResponse = (data) => { if (isErrorResponse(data.error)) { diff --git a/app/assets/javascripts/ide/remote/index.js b/app/assets/javascripts/ide/remote/index.js index fb8db20c0c1..6966786ca4e 100644 --- a/app/assets/javascripts/ide/remote/index.js +++ b/app/assets/javascripts/ide/remote/index.js @@ -1,6 +1,7 @@ import { startRemote } from '@gitlab/web-ide'; import { getBaseConfig, setupRootElement } from '~/ide/lib/gitlab_web_ide'; import { isSameOriginUrl, joinPaths } from '~/lib/utils/url_utility'; +import { handleTracking } from '~/ide/lib/gitlab_web_ide/handle_tracking_event'; /** * @param {Element} rootEl @@ -36,5 +37,6 @@ export const mountRemoteIDE = async (el) => { // TODO Handle error better handleError: visitReturnUrl, handleClose: visitReturnUrl, + handleTracking, }); }; diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index dc0f3a1d7e9..b7445d3ad0a 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -1,6 +1,7 @@ import { escape } from 'lodash'; import Vue from 'vue'; import { createAlert } from '~/flash'; +import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; import { __, sprintf } from '~/locale'; import { @@ -278,7 +279,7 @@ export const getBranchData = ({ commit, state }, { projectId, branchId, force = resolve(data); }) .catch((e) => { - if (e.response.status === 404) { + if (e.response.status === HTTP_STATUS_NOT_FOUND) { reject(e); } else { createAlert({ diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index 3c02b1d1da7..c0f666c6652 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -2,7 +2,6 @@ import Api from '~/api'; import { addNumericSuffix } from '~/ide/utils'; import { leftSidebarViews, - packageJsonPath, DEFAULT_PERMISSIONS, PERMISSION_READ_MR, PERMISSION_CREATE_MR, @@ -153,8 +152,6 @@ export const currentBranch = (state, getters) => export const branchName = (_state, getters) => getters.currentBranch && getters.currentBranch.name; -export const packageJson = (state) => state.entries[packageJsonPath]; - export const isOnDefaultBranch = (_state, getters) => getters.currentProject && getters.currentProject.default_branch === getters.branchName; diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js index b660ff178a2..c2f7126159c 100644 --- a/app/assets/javascripts/ide/stores/index.js +++ b/app/assets/javascripts/ide/stores/index.js @@ -3,7 +3,6 @@ import Vuex from 'vuex'; import * as actions from './actions'; import * as getters from './getters'; import branches from './modules/branches'; -import clientsideModule from './modules/clientside'; import commitModule from './modules/commit'; import editorModule from './modules/editor'; import { setupFileEditorsSync } from './modules/editor/setup'; @@ -29,7 +28,6 @@ export const createStoreOptions = () => ({ branches, fileTemplates: fileTemplates(), rightPane: paneModule(), - clientside: clientsideModule(), router: routerModule, editor: editorModule, }, diff --git a/app/assets/javascripts/ide/stores/modules/clientside/actions.js b/app/assets/javascripts/ide/stores/modules/clientside/actions.js deleted file mode 100644 index 1a8e665867f..00000000000 --- a/app/assets/javascripts/ide/stores/modules/clientside/actions.js +++ /dev/null @@ -1,11 +0,0 @@ -import axios from '~/lib/utils/axios_utils'; - -export const pingUsage = ({ rootGetters }, metricName) => { - const { web_url: projectUrl } = rootGetters.currentProject; - - const url = `${projectUrl}/service_ping/${metricName}`; - - return axios.post(url); -}; - -export default pingUsage; diff --git a/app/assets/javascripts/ide/stores/modules/clientside/index.js b/app/assets/javascripts/ide/stores/modules/clientside/index.js deleted file mode 100644 index b28f7b935a8..00000000000 --- a/app/assets/javascripts/ide/stores/modules/clientside/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import * as actions from './actions'; - -export default () => ({ - namespaced: true, - actions, -}); diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index b89d9d38a1a..356bbf28a48 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -26,10 +26,8 @@ export default () => ({ path: '', entry: {}, }, - clientsidePreviewEnabled: false, renderWhitespaceInCode: false, editorTheme: DEFAULT_THEME, - codesandboxBundlerUrl: null, environmentsGuidanceAlertDismissed: false, environmentsGuidanceAlertDetected: false, previewMarkdownPath: '', |