Mobile quality-of-life (part 1) (#2013)

This commit is contained in:
Zach Allaun 2023-06-28 09:29:44 -07:00 committed by GitHub
parent 4899767673
commit e1ac442c8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 130 additions and 50 deletions

View file

@ -166,6 +166,15 @@ Also some spacing adjustments.
.doctest-status-decoration-failed { .doctest-status-decoration-failed {
height: 100%; height: 100%;
position: relative; 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, .doctest-status-decoration-running::after,
@ -175,8 +184,8 @@ Also some spacing adjustments.
border-radius: 2px; border-radius: 2px;
content: ""; content: "";
display: block; display: block;
height: 12px; height: var(--decoration-size);
width: 12px; width: var(--decoration-size);
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;

View file

@ -16,7 +16,11 @@ import { morphdomOptions } from "./dom";
import { loadUserData } from "./lib/user"; import { loadUserData } from "./lib/user";
import { loadAppAuthToken } from "./lib/app"; import { loadAppAuthToken } from "./lib/app";
import { settingsStore } from "./lib/settings"; import { settingsStore } from "./lib/settings";
import { registerTopbar, registerGlobalEventHandlers } from "./events"; import {
registerTopbar,
registerGlobalEventHandlers,
disableZoomOnInputFocus,
} from "./events";
import { cookieOptions } from "./lib/utils"; import { cookieOptions } from "./lib/utils";
import { import {
loadConfirmOptOutIds, loadConfirmOptOutIds,
@ -54,6 +58,8 @@ function connect() {
registerGlobalEventHandlersForConfirm(); registerGlobalEventHandlersForConfirm();
disableZoomOnInputFocus();
// Reflect global configuration in attributes to enable CSS rules // Reflect global configuration in attributes to enable CSS rules
settingsStore.getAndSubscribe((settings) => { settingsStore.getAndSubscribe((settings) => {
document.body.setAttribute("data-editor-theme", settings.editor_theme); document.body.setAttribute("data-editor-theme", settings.editor_theme);

View file

@ -98,3 +98,27 @@ export function registerGlobalEventHandlers() {
{ capture: true } { 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";
}
}
}

View file

@ -88,6 +88,14 @@ const Cell = {
`cells:${this.props.cellId}`, `cells:${this.props.cellId}`,
(event) => this.handleCellEvent(event) (event) => this.handleCellEvent(event)
); );
// DOM events
this._handleViewportResize = this.handleViewportResize.bind(this);
window.visualViewport.addEventListener(
"resize",
this._handleViewportResize
);
}, },
disconnected() { disconnected() {
@ -99,6 +107,11 @@ const Cell = {
this.unsubscribeFromNavigationEvents(); this.unsubscribeFromNavigationEvents();
this.unsubscribeFromCellsEvents(); this.unsubscribeFromCellsEvents();
this.unsubscribeFromCellEvents(); this.unsubscribeFromCellEvents();
window.visualViewport.removeEventListener(
"resize",
this._handleViewportResize
);
}, },
updated() { updated() {
@ -266,6 +279,12 @@ const Cell = {
delete this.liveEditors[tag]; delete this.liveEditors[tag];
}, },
handleViewportResize() {
if (this.isFocused) {
this.scrollActiveElementIntoView();
}
},
currentEditor() { currentEditor() {
return this.liveEditors[this.currentEditorTag()]; return this.liveEditors[this.currentEditorTag()];
}, },
@ -325,13 +344,7 @@ const Cell = {
// sets new cursor position. To achieve this, we simply put this task // sets new cursor position. To achieve this, we simply put this task
// at the end of event loop, ensuring the editor mousedown handler is // at the end of event loop, ensuring the editor mousedown handler is
// executed first // executed first
setTimeout(() => { setTimeout(this.scrollActiveElementIntoView.bind(this), 0);
scrollIntoView(document.activeElement, {
scrollMode: "if-needed",
behavior: "smooth",
block: "center",
});
}, 0);
this.broadcastSelection(); this.broadcastSelection();
} }
@ -407,6 +420,14 @@ const Cell = {
}); });
} }
}, },
scrollActiveElementIntoView() {
scrollIntoView(document.activeElement, {
scrollMode: "if-needed",
behavior: "smooth",
block: "center",
});
},
}; };
export default Cell; export default Cell;

View file

@ -310,6 +310,8 @@ class LiveEditor {
: "off", : "off",
}); });
this._setScreenDependantEditorOptions();
this.editor.addAction({ this.editor.addAction({
contextMenuGroupId: "word-wrapping", contextMenuGroupId: "word-wrapping",
id: "enable-word-wrapping", id: "enable-word-wrapping",
@ -333,6 +335,7 @@ class LiveEditor {
entries.forEach((entry) => { entries.forEach((entry) => {
// Ignore hidden container. // Ignore hidden container.
if (this.container.offsetHeight > 0) { if (this.container.offsetHeight > 0) {
this._setScreenDependantEditorOptions();
this.editor.layout(); this.editor.layout();
} }
}); });
@ -366,6 +369,26 @@ class LiveEditor {
this._initializeWidgets(); 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. * Defines cell-specific providers for various editor features.
*/ */

View file

@ -95,6 +95,7 @@ class DetailsWidget {
"editor-theme-aware-ansi" "editor-theme-aware-ansi"
); );
detailsNode.style.fontSize = `${fontSize}px`; detailsNode.style.fontSize = `${fontSize}px`;
detailsNode.style.lineHeight = `${lineHeight}px`;
this._overlayWidget = { this._overlayWidget = {
getId: () => `livebook.doctest.overlay.${line}`, getId: () => `livebook.doctest.overlay.${line}`,

View file

@ -70,7 +70,7 @@ import "monaco-editor/esm/vs/editor/contrib/readOnlyMessage/browser/contribution
// import 'vs/base/browser/ui/codicons/codiconStyles'; // 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/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/inspectTokens/inspectTokens";
import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess"; import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess";
import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess"; import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess";

View file

@ -7,7 +7,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<div <div
class="flex flex-col relative" class="flex flex-col relative scroll-mt-[50px] sm:scroll-mt-0"
data-el-cell data-el-cell
id={"cell-#{@cell_view.id}"} id={"cell-#{@cell_view.id}"}
phx-hook="Cell" phx-hook="Cell"
@ -268,7 +268,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
<%= render_slot(@primary) %> <%= render_slot(@primary) %>
</div> </div>
<div <div
class="relative z-20 flex items-center justify-end space-x-2" class="relative z-20 flex items-center justify-end md:space-x-2"
role="toolbar" role="toolbar"
aria-label="cell actions" aria-label="cell actions"
data-el-actions data-el-actions

View file

@ -14,10 +14,8 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
data-el-insert-buttons data-el-insert-buttons
> >
<div class={ <div class={
"w-full absolute z-10 hover:z-[11] #{if(@persistent, do: "opacity-100", else: "opacity-0")} hover:opacity-100 focus-within:opacity-100 flex space-x-2 justify-center items-center" "w-full md:absolute z-10 hover:z-[11] #{if(@persistent, do: "opacity-100", else: "opacity-0")} hover:opacity-100 focus-within:opacity-100 flex space-x-2 justify-center items-center"
}> }>
<div class="relative">
<div class="absolute -left-1 top-0 bottom-0 flex items-center transform -translate-x-full">
<.menu id={"cell-#{@id}-insert"} position={:bottom_left} distant> <.menu id={"cell-#{@id}-insert"} position={:bottom_left} distant>
<:toggle> <:toggle>
<button class="button-base button-small flex items-center pr-1"> <button class="button-base button-small flex items-center pr-1">
@ -49,8 +47,6 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
</button> </button>
</.menu_item> </.menu_item>
</.menu> </.menu>
</div>
</div>
<.menu id={"#{@id}-block-menu"} position={:bottom_left}> <.menu id={"#{@id}-block-menu"} position={:bottom_left}>
<:toggle> <:toggle>
<button class="button-base button-small">+ Block</button> <button class="button-base button-small">+ Block</button>

View file

@ -42,7 +42,7 @@ defmodule LivebookWeb.SessionLive.SectionComponent do
</button> </button>
</div> </div>
<h2 <h2
class="grow text-gray-800 font-semibold text-2xl px-1 -ml-1.5 rounded-lg border border-transparent whitespace-pre-wrap cursor-text" class="grow text-gray-800 font-semibold text-2xl px-1 -ml-1.5 rounded-lg border border-transparent whitespace-pre-wrap cursor-text scroll-mt-[50px] sm:scroll-mt-0"
tabindex="0" tabindex="0"
id={@section_view.html_id} id={@section_view.html_id}
data-el-heading data-el-heading