diff --git a/assets/css/editor.css b/assets/css/editor.css index 16048aac5..3b522240c 100644 --- a/assets/css/editor.css +++ b/assets/css/editor.css @@ -166,6 +166,15 @@ Also some spacing adjustments. .doctest-status-decoration-failed { height: 100%; position: relative; + --decoration-size: 10px; +} + +@media screen(md) { + .doctest-status-decoration-running, + .doctest-status-decoration-success, + .doctest-status-decoration-failed { + --decoration-size: 12px; + } } .doctest-status-decoration-running::after, @@ -175,8 +184,8 @@ Also some spacing adjustments. border-radius: 2px; content: ""; display: block; - height: 12px; - width: 12px; + height: var(--decoration-size); + width: var(--decoration-size); position: absolute; top: 50%; left: 50%; diff --git a/assets/js/app.js b/assets/js/app.js index 30f6e84c1..9cc3b56be 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -16,7 +16,11 @@ import { morphdomOptions } from "./dom"; import { loadUserData } from "./lib/user"; import { loadAppAuthToken } from "./lib/app"; import { settingsStore } from "./lib/settings"; -import { registerTopbar, registerGlobalEventHandlers } from "./events"; +import { + registerTopbar, + registerGlobalEventHandlers, + disableZoomOnInputFocus, +} from "./events"; import { cookieOptions } from "./lib/utils"; import { loadConfirmOptOutIds, @@ -54,6 +58,8 @@ function connect() { registerGlobalEventHandlersForConfirm(); + disableZoomOnInputFocus(); + // Reflect global configuration in attributes to enable CSS rules settingsStore.getAndSubscribe((settings) => { document.body.setAttribute("data-editor-theme", settings.editor_theme); diff --git a/assets/js/events.js b/assets/js/events.js index 85900e710..473ed5a64 100644 --- a/assets/js/events.js +++ b/assets/js/events.js @@ -98,3 +98,27 @@ export function registerGlobalEventHandlers() { { capture: true } ); } + +/** + * Disables the auto-zoom behavior when focusing an input on a touch device. + * + * It is important that this should not prevent users from manually + * zooming if they wish. There isn't a portable solution to this + * problem, so this hook is a no-op if the detected device is not + * known to behave well. + * + * See: https://stackoverflow.com/questions/2989263/disable-auto-zoom-in-input-text-tag-safari-on-iphone + */ +export function disableZoomOnInputFocus() { + const isWebKit = /AppleWebKit/.test(navigator.userAgent); + const isTouchScreen = + "ontouchstart" in window || navigator.maxTouchPoints > 0; + + if (isWebKit && isTouchScreen) { + const viewportTag = document.querySelector("meta[name='viewport']"); + + if (viewportTag) { + viewportTag.content += ", maximum-scale=1.0"; + } + } +} diff --git a/assets/js/hooks/cell.js b/assets/js/hooks/cell.js index 5a637555c..fd8eb2945 100644 --- a/assets/js/hooks/cell.js +++ b/assets/js/hooks/cell.js @@ -88,6 +88,14 @@ const Cell = { `cells:${this.props.cellId}`, (event) => this.handleCellEvent(event) ); + + // DOM events + + this._handleViewportResize = this.handleViewportResize.bind(this); + window.visualViewport.addEventListener( + "resize", + this._handleViewportResize + ); }, disconnected() { @@ -99,6 +107,11 @@ const Cell = { this.unsubscribeFromNavigationEvents(); this.unsubscribeFromCellsEvents(); this.unsubscribeFromCellEvents(); + + window.visualViewport.removeEventListener( + "resize", + this._handleViewportResize + ); }, updated() { @@ -266,6 +279,12 @@ const Cell = { delete this.liveEditors[tag]; }, + handleViewportResize() { + if (this.isFocused) { + this.scrollActiveElementIntoView(); + } + }, + currentEditor() { return this.liveEditors[this.currentEditorTag()]; }, @@ -325,13 +344,7 @@ const Cell = { // sets new cursor position. To achieve this, we simply put this task // at the end of event loop, ensuring the editor mousedown handler is // executed first - setTimeout(() => { - scrollIntoView(document.activeElement, { - scrollMode: "if-needed", - behavior: "smooth", - block: "center", - }); - }, 0); + setTimeout(this.scrollActiveElementIntoView.bind(this), 0); this.broadcastSelection(); } @@ -407,6 +420,14 @@ const Cell = { }); } }, + + scrollActiveElementIntoView() { + scrollIntoView(document.activeElement, { + scrollMode: "if-needed", + behavior: "smooth", + block: "center", + }); + }, }; export default Cell; diff --git a/assets/js/hooks/cell_editor/live_editor.js b/assets/js/hooks/cell_editor/live_editor.js index c517a2ce2..f9d7a5f72 100644 --- a/assets/js/hooks/cell_editor/live_editor.js +++ b/assets/js/hooks/cell_editor/live_editor.js @@ -310,6 +310,8 @@ class LiveEditor { : "off", }); + this._setScreenDependantEditorOptions(); + this.editor.addAction({ contextMenuGroupId: "word-wrapping", id: "enable-word-wrapping", @@ -333,6 +335,7 @@ class LiveEditor { entries.forEach((entry) => { // Ignore hidden container. if (this.container.offsetHeight > 0) { + this._setScreenDependantEditorOptions(); this.editor.layout(); } }); @@ -366,6 +369,26 @@ class LiveEditor { this._initializeWidgets(); } + /** + * Sets Monaco editor options that depend on the current screen's size. + */ + _setScreenDependantEditorOptions() { + if (window.screen.width < 768) { + this.editor.updateOptions({ + folding: false, + lineDecorationsWidth: 16, + lineNumbersMinChars: + Math.floor(Math.log10(this.editor.getModel().getLineCount())) + 3, + }); + } else { + this.editor.updateOptions({ + folding: true, + lineDecorationsWidth: 10, + lineNumbersMinChars: 5, + }); + } + } + /** * Defines cell-specific providers for various editor features. */ diff --git a/assets/js/hooks/cell_editor/live_editor/doctest.js b/assets/js/hooks/cell_editor/live_editor/doctest.js index c407abf77..60e423253 100644 --- a/assets/js/hooks/cell_editor/live_editor/doctest.js +++ b/assets/js/hooks/cell_editor/live_editor/doctest.js @@ -95,6 +95,7 @@ class DetailsWidget { "editor-theme-aware-ansi" ); detailsNode.style.fontSize = `${fontSize}px`; + detailsNode.style.lineHeight = `${lineHeight}px`; this._overlayWidget = { getId: () => `livebook.doctest.overlay.${line}`, diff --git a/assets/js/hooks/cell_editor/live_editor/monaco.js b/assets/js/hooks/cell_editor/live_editor/monaco.js index e44454052..171525fbb 100644 --- a/assets/js/hooks/cell_editor/live_editor/monaco.js +++ b/assets/js/hooks/cell_editor/live_editor/monaco.js @@ -70,7 +70,7 @@ import "monaco-editor/esm/vs/editor/contrib/readOnlyMessage/browser/contribution // import 'vs/base/browser/ui/codicons/codiconStyles'; import "monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp"; -import "monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard"; +// import "monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard"; import "monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens"; import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess"; import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess"; diff --git a/lib/livebook_web/live/session_live/cell_component.ex b/lib/livebook_web/live/session_live/cell_component.ex index e641617d2..0ee73937c 100644 --- a/lib/livebook_web/live/session_live/cell_component.ex +++ b/lib/livebook_web/live/session_live/cell_component.ex @@ -7,7 +7,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do def render(assigns) do ~H"""