overleaf/services/web/frontend/js/ide/settings/controllers/SettingsController.js
Jakob Ackermann 8ba1803972 Merge pull request #5625 from overleaf/jpa-block-update-loop
[web] block a loop of failing rootDoc update requests

GitOrigin-RevId: 1388024fc125ee1e80e45a7bbbc787306cb60d1d
2021-11-03 09:03:09 +00:00

250 lines
7.8 KiB
JavaScript

import _ from 'lodash'
/* eslint-disable
camelcase,
max-len,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import App from '../../../base'
import isValidTeXFile from '../../../main/is-valid-tex-file'
export default App.controller(
'SettingsController',
function ($scope, settings, ide) {
$scope.overallThemesList = window.overallThemes
$scope.ui = { loadingStyleSheet: false }
const _updateCSSFile = function (theme) {
$scope.ui.loadingStyleSheet = true
const docHeadEl = document.querySelector('head')
const oldStyleSheetEl = document.getElementById('main-stylesheet')
const newStyleSheetEl = document.createElement('link')
newStyleSheetEl.addEventListener('load', e => {
return $scope.$applyAsync(() => {
$scope.ui.loadingStyleSheet = false
return docHeadEl.removeChild(oldStyleSheetEl)
})
})
newStyleSheetEl.setAttribute('rel', 'stylesheet')
newStyleSheetEl.setAttribute('id', 'main-stylesheet')
newStyleSheetEl.setAttribute('href', theme.path)
return docHeadEl.appendChild(newStyleSheetEl)
}
if (!['default', 'vim', 'emacs'].includes($scope.settings.mode)) {
$scope.settings.mode = 'default'
}
if (!['pdfjs', 'native'].includes($scope.settings.pdfViewer)) {
$scope.settings.pdfViewer = 'pdfjs'
}
if (
$scope.settings.fontFamily != null &&
!['monaco', 'lucida'].includes($scope.settings.fontFamily)
) {
delete $scope.settings.fontFamily
}
if (
$scope.settings.lineHeight != null &&
!['compact', 'normal', 'wide'].includes($scope.settings.lineHeight)
) {
delete $scope.settings.lineHeight
}
$scope.fontSizeAsStr = function (newVal) {
if (newVal != null) {
$scope.settings.fontSize = newVal
}
return $scope.settings.fontSize.toString()
}
$scope.getValidMainDocs = () => {
let filteredDocs = []
if ($scope.docs) {
// Filter the existing docs (editable files) by accepted file extensions.
// It's possible that an existing project has an invalid file selected as the main one.
// To gracefully handle that case, make sure we also show the current main file (ignoring extension).
filteredDocs = $scope.docs.filter(
doc =>
isValidTeXFile(doc.doc.name) ||
$scope.project.rootDoc_id === doc.doc.id
)
}
return filteredDocs
}
$scope.$watch('settings.editorTheme', (editorTheme, oldEditorTheme) => {
if (editorTheme !== oldEditorTheme) {
return settings.saveSettings({ editorTheme })
}
})
$scope.$watch('settings.overallTheme', (overallTheme, oldOverallTheme) => {
if (overallTheme !== oldOverallTheme) {
const chosenTheme = _.find(
$scope.overallThemesList,
theme => theme.val === overallTheme
)
if (chosenTheme != null) {
_updateCSSFile(chosenTheme)
return settings.saveSettings({ overallTheme })
}
}
})
$scope.$watch('settings.fontSize', (fontSize, oldFontSize) => {
if (fontSize !== oldFontSize) {
return settings.saveSettings({ fontSize: parseInt(fontSize, 10) })
}
})
$scope.$watch('settings.mode', (mode, oldMode) => {
if (mode !== oldMode) {
return settings.saveSettings({ mode })
}
})
$scope.$watch('settings.autoComplete', (autoComplete, oldAutoComplete) => {
if (autoComplete !== oldAutoComplete) {
return settings.saveSettings({ autoComplete })
}
})
$scope.$watch(
'settings.autoPairDelimiters',
(autoPairDelimiters, oldAutoPairDelimiters) => {
if (autoPairDelimiters !== oldAutoPairDelimiters) {
return settings.saveSettings({ autoPairDelimiters })
}
}
)
$scope.$watch('settings.pdfViewer', (pdfViewer, oldPdfViewer) => {
if (pdfViewer !== oldPdfViewer) {
return settings.saveSettings({ pdfViewer })
}
})
$scope.$watch(
'settings.syntaxValidation',
(syntaxValidation, oldSyntaxValidation) => {
if (syntaxValidation !== oldSyntaxValidation) {
return settings.saveSettings({ syntaxValidation })
}
}
)
$scope.$watch('settings.fontFamily', (fontFamily, oldFontFamily) => {
if (fontFamily !== oldFontFamily) {
return settings.saveSettings({ fontFamily })
}
})
$scope.$watch('settings.lineHeight', (lineHeight, oldLineHeight) => {
if (lineHeight !== oldLineHeight) {
return settings.saveSettings({ lineHeight })
}
})
$scope.$watch('project.spellCheckLanguage', (language, oldLanguage) => {
if (this.ignoreUpdates) {
return
}
if (oldLanguage != null && language !== oldLanguage) {
settings.saveProjectSettings({ spellCheckLanguage: language })
// Also set it as the default for the user
return settings.saveSettings({ spellCheckLanguage: language })
}
})
$scope.$watch('project.compiler', (compiler, oldCompiler) => {
if (this.ignoreUpdates) {
return
}
if (oldCompiler != null && compiler !== oldCompiler) {
return settings.saveProjectSettings({ compiler })
}
})
$scope.$watch('project.imageName', (imageName, oldImageName) => {
if (this.ignoreUpdates) {
return
}
if (oldImageName != null && imageName !== oldImageName) {
return settings.saveProjectSettings({ imageName })
}
})
let rootDocUpdateFailed = 0
$scope.$watch('project.rootDoc_id', (rootDoc_id, oldRootDoc_id) => {
if (this.ignoreUpdates) {
return
}
// don't save on initialisation, Angular passes oldRootDoc_id as
// undefined in this case.
if (typeof oldRootDoc_id === 'undefined') {
return
}
if ($scope.permissionsLevel === 'readOnly') {
// The user is unauthorized to persist rootDoc changes.
// Use the new value for this very editor session only.
return
}
// otherwise only save changes, null values are allowed
if (rootDoc_id !== oldRootDoc_id) {
settings
.saveProjectSettings({ rootDocId: rootDoc_id })
.then(() => {
rootDocUpdateFailed = 0
})
.catch(() => {
rootDocUpdateFailed++
// Let the login redirect run (if any) and reset afterwards.
setTimeout(() => {
if (rootDocUpdateFailed > 10) {
// We are in a loop of failing updates. Stop now.
this.ignoreUpdates = true
}
$scope.$apply(() => {
$scope.project.rootDoc_id = oldRootDoc_id
})
this.ignoreUpdates = false
})
})
}
})
ide.socket.on('compilerUpdated', compiler => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.compiler = compiler)
})
return delete this.ignoreUpdates
})
ide.socket.on('imageNameUpdated', imageName => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.imageName = imageName)
})
return delete this.ignoreUpdates
})
return ide.socket.on('spellCheckLanguageUpdated', languageCode => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.spellCheckLanguage = languageCode)
})
return delete this.ignoreUpdates
})
}
)