diff --git a/browser/Makefile.am b/browser/Makefile.am index fe7d3bfa6..20f199dac 100644 --- a/browser/Makefile.am +++ b/browser/Makefile.am @@ -314,6 +314,7 @@ COOL_JS_LST =\ src/map/handler/Map.StateChanges.js \ src/map/handler/Map.WOPI.js \ src/layer/marker/Marker.Drag.js \ + src/control/Control.AboutDialog.ts \ src/control/Control.Toolbar.js \ src/control/Control.Command.js \ src/control/Control.js \ @@ -764,6 +765,7 @@ pot: admin/src/Util.js \ js/global.js \ src/control/ColorPicker.ts \ + src/control/Control.AboutDialog.ts \ src/control/Control.AlertDialog.js \ src/control/Control.Command.js \ src/control/Control.ContextMenu.js \ diff --git a/browser/src/control/Control.AboutDialog.ts b/browser/src/control/Control.AboutDialog.ts new file mode 100644 index 000000000..de674c66e --- /dev/null +++ b/browser/src/control/Control.AboutDialog.ts @@ -0,0 +1,265 @@ +/* + * Copyright the Collabora Online contributors. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* + * AboutDialog - implements Help - About dialog with version and warnings + */ + +declare var JSDialog: any; +declare var brandProductName: any; +declare var brandProductURL: any; +declare var sanitizeUrl: any; + +class AboutDialog { + map: any; + + constructor(map: any) { + this.map = map; + } + + private aboutDialogClickHandler(e: any) { + if (e.detail === 3) { + this.map._debug.toggle(); + } + } + + public show() { + const windowAny = window as any; + // Just as a test to exercise the Async Trace Event functionality, uncomment this + // line and the asyncTraceEvent.finish() below. + // var asyncTraceEvent = app.socket.createAsyncTraceEvent('cool-showLOAboutDialog'); + + const aboutDialogId = 'about-dialog'; + // Move the div sitting in 'body' as content and make it visible + const content: HTMLElement = document + .getElementById(aboutDialogId) + .cloneNode(true) as HTMLElement; + content.style.display = 'block'; + + // fill product-name and product-string + let productName; + if (windowAny.ThisIsAMobileApp) { + productName = windowAny.MobileAppName; + } else { + productName = + typeof brandProductName !== 'undefined' + ? brandProductName + : 'Collabora Online Development Edition (unbranded)'; + } + var productURL = + typeof brandProductURL !== 'undefined' + ? brandProductURL + : 'https://collaboraonline.github.io/'; + + const productNameElement = content.querySelector( + '#product-name', + ) as HTMLElement; + productNameElement.innerText = productName; + content.classList.add( + 'product-' + + productName + .split(/[ ()]+/) + .join('-') + .toLowerCase(), + ); + + var productString = _('This version of %productName is powered by'); + var productNameWithURL; + if (!windowAny.ThisIsAMobileApp) + productNameWithURL = + '' + + productName + + ''; + else productNameWithURL = productName; + + const productStringElement = content.querySelector( + '#product-string', + ) as HTMLElement; + if (productStringElement) + productStringElement.innerText = productString.replace( + '%productName', + productNameWithURL, + ); + + const slowProxyElement = content.querySelector( + '#slow-proxy', + ) as HTMLElement; + if (windowAny.socketProxy) slowProxyElement.innerText = _('"Slow Proxy"'); + + const routeTokenElement = content.querySelector( + '#routeToken', + ) as HTMLElement; + if (windowAny.indirectSocket) + routeTokenElement.innerText = 'RouteToken: ' + windowAny.routeToken; + + this.map.uiManager.showYesNoButton( + aboutDialogId + '-box', + productName, + '', + _('OK'), + null, + null, + null, + true, + ); + var box = document.getElementById(aboutDialogId + '-box'); + var innerDiv = L.DomUtil.create('div', '', null); + box.insertBefore(innerDiv, box.firstChild); + innerDiv.innerHTML = content.outerHTML; + + var form = document.getElementById('about-dialog-box'); + + form.addEventListener('click', this.aboutDialogClickHandler.bind(this)); + form.addEventListener('keyup', this.aboutDialogKeyHandler.bind(this)); + form.querySelector('#coolwsd-version').querySelector('a').focus(); + var copyversion = L.DomUtil.create( + 'button', + 'ui-pushbutton jsdialog', + null, + ); + copyversion.setAttribute('id', 'modal-dialog-about-dialog-box-copybutton'); + copyversion.setAttribute( + 'title', + _('Copy all version information in English'), + ); + var img = L.DomUtil.create('img', null, null); + L.LOUtil.setImage(img, 'lc_copy.svg', this.map); + copyversion.innerHTML = + ''; + copyversion.addEventListener( + 'click', + this.copyVersionInfoToClipboard.bind(this), + ); + this.map.uiManager.enableTooltip(copyversion); + var aboutok = document.getElementById( + 'modal-dialog-about-dialog-box-yesbutton', + ); + if (aboutok) { + aboutok.before(copyversion); + } + } + + private aboutDialogKeyHandler(e: KeyboardEvent) { + if (e.key === 'd') { + this.map._debug.toggle(); + } else if (e.key === 'l') { + // L toggges the Online logging level between the default (whatever + // is set in coolwsd.xml or on the coolwsd command line) and the + // most verbose a client is allowed to set (which also can be set in + // coolwsd.xml or on the coolwsd command line). + // + // In a typical developer "make run" setup, the default is "trace" + // so there is nothing more verbose. But presumably it is different + // in production setups. + + app.socket.threadLocalLoggingLevelToggle = + !app.socket.threadLocalLoggingLevelToggle; + + const newLogLevel = app.socket.threadLocalLoggingLevelToggle + ? 'verbose' + : 'default'; + + app.socket.sendMessage('loggingleveloverride ' + newLogLevel); + + let logLevelInformation; + if (newLogLevel === 'default') + logLevelInformation = 'default (from coolwsd.xml)'; + else if (newLogLevel === 'verbose') + logLevelInformation = 'most verbose (from coolwsd.xml)'; + else if (newLogLevel === 'terse') + logLevelInformation = 'least verbose (from coolwsd.xml)'; + else logLevelInformation = newLogLevel; + + console.debug('Log level: ' + logLevelInformation); + } + } + + private copyVersionInfoToClipboard() { + let text = + 'COOLWSD version: ' + + this.getVersionInfoFromClass('coolwsd-version') + + '\n'; + text += + 'LOKit version: ' + this.getVersionInfoFromClass('lokit-version') + '\n'; + text += 'Served by: ' + document.getElementById('os-info').innerText + '\n'; + text += + 'Server ID: ' + document.getElementById('coolwsd-id').innerText + '\n'; + text = text.replace(/\u00A0/g, ' '); + + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard + .writeText(text) + .then( + function () { + window.console.log('Text copied to clipboard'); + this.contentHasBeenCopiedShowSnackbar(); + }.bind(this), + ) + .catch(function (error) { + window.console.error('Error copying text to clipboard:', error); + }); + } else { + var textArea = document.createElement('textarea'); + textArea.style.position = 'absolute'; + textArea.style.opacity = '0'; + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + try { + document.execCommand('copy'); + window.console.log('Text copied to clipboard'); + this.contentHasBeenCopiedShowSnackbar(); + } catch (error) { + window.console.error('Error copying text to clipboard:', error); + } finally { + document.body.removeChild(textArea); + } + } + } + + private contentHasBeenCopiedShowSnackbar() { + const timeout = 1000; + this.map.uiManager.showSnackbar( + 'Version information has been copied', + null, + null, + timeout, + ); + const copybutton = document.querySelector( + '#modal-dialog-about-dialog-box-copybutton > img', + ); + L.LOUtil.setImage(copybutton, 'lc_clipboard-check.svg', this.map); + setTimeout(() => { + L.LOUtil.setImage(copybutton, 'lc_copy.svg', this.map); + }, timeout); + } + + private getVersionInfoFromClass(className: string) { + const versionElement = document.getElementById(className); + let versionInfo = versionElement.innerText; + + const gitHashIndex = versionInfo.indexOf('git hash'); + if (gitHashIndex > -1) { + versionInfo = + versionInfo.slice(0, gitHashIndex) + + '(' + + versionInfo.slice(gitHashIndex) + + ')'; + } + + return versionInfo; + } +} + +// Initiate the class. +JSDialog.aboutDialog = (map: any) => { + return new AboutDialog(map); +}; diff --git a/browser/src/control/Control.UIManager.js b/browser/src/control/Control.UIManager.js index 0f041c6be..771534740 100644 --- a/browser/src/control/Control.UIManager.js +++ b/browser/src/control/Control.UIManager.js @@ -236,6 +236,7 @@ L.Control.UIManager = L.Control.extend({ this.map.addControl(L.control.contextMenu()); this.map.userList = L.control.userList(); this.map.addControl(this.map.userList); + this.map.aboutDialog = JSDialog.aboutDialog(this.map); var openBusyPopup = function(label) { this.busyPopupTimer = setTimeout(function() { diff --git a/browser/src/control/Toolbar.js b/browser/src/control/Toolbar.js index c09cb68f5..80ea32255 100644 --- a/browser/src/control/Toolbar.js +++ b/browser/src/control/Toolbar.js @@ -12,7 +12,7 @@ * Toolbar handler */ -/* global app $ window sanitizeUrl brandProductName brandProductURL _ */ +/* global app $ window brandProductName _ */ L.Map.include({ // a mapping of uno commands to more readable toolbar items @@ -724,165 +724,8 @@ L.Map.include({ }); }, - aboutDialogKeyHandler: function(event) { - if (event.key === 'd') { - this._debug.toggle(); - } else if (event.key === 'l') { - // L toggges the Online logging level between the default (whatever - // is set in coolwsd.xml or on the coolwsd command line) and the - // most verbose a client is allowed to set (which also can be set in - // coolwsd.xml or on the coolwsd command line). - // - // In a typical developer "make run" setup, the default is "trace" - // so there is nothing more verbose. But presumably it is different - // in production setups. - - app.socket.threadLocalLoggingLevelToggle = !app.socket.threadLocalLoggingLevelToggle; - - var newLogLevel = (app.socket.threadLocalLoggingLevelToggle ? 'verbose' : 'default'); - - app.socket.sendMessage('loggingleveloverride ' + newLogLevel); - - var logLevelInformation; - if (newLogLevel === 'default') - logLevelInformation = 'default (from coolwsd.xml)'; - else if (newLogLevel === 'verbose') - logLevelInformation = 'most verbose (from coolwsd.xml)'; - else if (newLogLevel === 'terse') - logLevelInformation = 'least verbose (from coolwsd.xml)'; - else - logLevelInformation = newLogLevel; - - console.debug('Log level: ' + logLevelInformation); - } - }, - - aboutDialogClickHandler: function(event) { - if (event.detail === 3) { - this._debug.toggle(); - } - }, - showLOAboutDialog: function() { - // Just as a test to exercise the Async Trace Event functionality, uncomment this - // line and the asyncTraceEvent.finish() below. - // var asyncTraceEvent = app.socket.createAsyncTraceEvent('cool-showLOAboutDialog'); - - var aboutDialogId = 'about-dialog'; - // Move the div sitting in 'body' as content and make it visible - var content = document.getElementById(aboutDialogId).cloneNode(true); - content.style.display = 'block'; - - // fill product-name and product-string - var productName; - if (window.ThisIsAMobileApp) { - productName = window.MobileAppName; - } else { - productName = (typeof brandProductName !== 'undefined') ? brandProductName : 'Collabora Online Development Edition (unbranded)'; - } - var productURL = (typeof brandProductURL !== 'undefined') ? brandProductURL : 'https://collaboraonline.github.io/'; - - content.querySelector('#product-name').innerText = productName; - content.classList.add('product-' + productName.split(/[ ()]+/).join('-').toLowerCase()); - - var productString = _('This version of %productName is powered by'); - var productNameWithURL; - if (!window.ThisIsAMobileApp) - productNameWithURL = '' + productName + ''; - else - productNameWithURL = productName; - - if (content.querySelector('#product-string')) - content.querySelector('#product-string').innerText = productString.replace('%productName', productNameWithURL); - - if (window.socketProxy) - content.querySelector('#slow-proxy').innerText = _('"Slow Proxy"'); - - var map = this; - if (window.indirectSocket) - content.querySelector('#routeToken').innerText = 'RouteToken: ' + window.routeToken; - - map.uiManager.showYesNoButton(aboutDialogId + '-box', productName, '', _('OK'), null, null, null, true); - var box = document.getElementById(aboutDialogId + '-box'); - var innerDiv = L.DomUtil.create('div', '', null); - box.insertBefore(innerDiv, box.firstChild); - innerDiv.innerHTML = content.outerHTML; - - var form = document.getElementById('about-dialog-box'); - - form.addEventListener('click', this.aboutDialogClickHandler.bind(this)); - form.addEventListener('keyup', this.aboutDialogKeyHandler.bind(this)); - form.querySelector('#coolwsd-version').querySelector('a').focus(); - var copyversion = L.DomUtil.create('button', 'ui-pushbutton jsdialog', null); - copyversion.setAttribute('id', 'modal-dialog-about-dialog-box-copybutton'); - copyversion.setAttribute('title', _('Copy all version information in English')); - var img = L.DomUtil.create('img', null, null); - L.LOUtil.setImage(img, 'lc_copy.svg', map); - copyversion.innerHTML = ''; - copyversion.addEventListener('click', this.copyVersionInfoToClipboard.bind(this)); - map.uiManager.enableTooltip(copyversion); - var aboutok = document.getElementById('modal-dialog-about-dialog-box-yesbutton'); - if (aboutok) { - aboutok.before(copyversion); - } - }, - - getVersionInfoFromClass: function(className) { - var versionElement = document.getElementById(className); - var versionInfo = versionElement.innerText; - - var gitHashIndex = versionInfo.indexOf('git hash'); - if (gitHashIndex > -1) { - versionInfo = versionInfo.slice(0, gitHashIndex) + '(' + versionInfo.slice(gitHashIndex) + ')'; - } - - return versionInfo; - }, - - copyVersionInfoToClipboard: function() { - var text = 'COOLWSD version: ' + this.getVersionInfoFromClass('coolwsd-version') + '\n'; - text += 'LOKit version: ' + this.getVersionInfoFromClass('lokit-version') + '\n'; - text += 'Served by: ' + document.getElementById('os-info').innerText + '\n'; - text += 'Server ID: ' + document.getElementById('coolwsd-id').innerText + '\n'; - text = text.replace(/\u00A0/g, ' '); - - if (navigator.clipboard && window.isSecureContext) { - navigator.clipboard.writeText(text) - .then(function() { - window.console.log('Text copied to clipboard'); - this.contentHasBeenCopiedShowSnackbar(); - }.bind(this)) - .catch(function(error) { - window.console.error('Error copying text to clipboard:', error); - }); - } else { - var textArea = document.createElement('textarea'); - textArea.style.position = 'absolute'; - textArea.style.opacity = 0; - textArea.value = text; - document.body.appendChild(textArea); - textArea.select(); - try { - document.execCommand('copy'); - window.console.log('Text copied to clipboard'); - this.contentHasBeenCopiedShowSnackbar(); - } catch (error) { - window.console.error('Error copying text to clipboard:', error); - } finally { - document.body.removeChild(textArea); - } - } - }, - - contentHasBeenCopiedShowSnackbar: function() { - var timeout = 1000; - this.uiManager.showSnackbar('Version information has been copied', null, null, timeout); - var copybutton = document.querySelector('#modal-dialog-about-dialog-box-copybutton > img'); - L.LOUtil.setImage(copybutton, 'lc_clipboard-check.svg', this); - setTimeout(function () { - L.LOUtil.setImage(copybutton, 'lc_copy.svg', this); - }.bind(this), timeout); + this.aboutDialog.show(); }, extractContent: function(html) {