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 {
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%;

View file

@ -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);

View file

@ -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";
}
}
}

View file

@ -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;

View file

@ -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.
*/

View file

@ -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}`,

View file

@ -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";

View file

@ -7,7 +7,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
def render(assigns) do
~H"""
<div
class="flex flex-col relative"
class="flex flex-col relative scroll-mt-[50px] sm:scroll-mt-0"
data-el-cell
id={"cell-#{@cell_view.id}"}
phx-hook="Cell"
@ -268,7 +268,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
<%= render_slot(@primary) %>
</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"
aria-label="cell actions"
data-el-actions

View file

@ -14,43 +14,39 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
data-el-insert-buttons
>
<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>
<:toggle>
<button class="button-base button-small flex items-center pr-1">
<div
class="pr-2"
phx-click="insert_cell_below"
phx-value-type="code"
phx-value-section_id={@section_id}
phx-value-cell_id={@cell_id}
phx-value-language="elixir"
>
+ <%= @default_language |> Atom.to_string() |> String.capitalize() %>
</div>
<div class="pl-1 flex items-center border-l border-gray-200">
<.remix_icon icon="arrow-down-s-line" class="text-lg leading-none" />
</div>
</button>
</:toggle>
<.menu_item>
<button role="menuitem" phx-click="set_default_language" phx-value-language="elixir">
<.cell_icon cell_type={:code} language={:elixir} />
<span>Elixir</span>
</button>
</.menu_item>
<.menu_item>
<button role="menuitem" phx-click="set_default_language" phx-value-language="erlang">
<.cell_icon cell_type={:code} language={:erlang} />
<span>Erlang</span>
</button>
</.menu_item>
</.menu>
</div>
</div>
<.menu id={"cell-#{@id}-insert"} position={:bottom_left} distant>
<:toggle>
<button class="button-base button-small flex items-center pr-1">
<div
class="pr-2"
phx-click="insert_cell_below"
phx-value-type="code"
phx-value-section_id={@section_id}
phx-value-cell_id={@cell_id}
phx-value-language="elixir"
>
+ <%= @default_language |> Atom.to_string() |> String.capitalize() %>
</div>
<div class="pl-1 flex items-center border-l border-gray-200">
<.remix_icon icon="arrow-down-s-line" class="text-lg leading-none" />
</div>
</button>
</:toggle>
<.menu_item>
<button role="menuitem" phx-click="set_default_language" phx-value-language="elixir">
<.cell_icon cell_type={:code} language={:elixir} />
<span>Elixir</span>
</button>
</.menu_item>
<.menu_item>
<button role="menuitem" phx-click="set_default_language" phx-value-language="erlang">
<.cell_icon cell_type={:code} language={:erlang} />
<span>Erlang</span>
</button>
</.menu_item>
</.menu>
<.menu id={"#{@id}-block-menu"} position={:bottom_left}>
<:toggle>
<button class="button-base button-small">+ Block</button>

View file

@ -42,7 +42,7 @@ defmodule LivebookWeb.SessionLive.SectionComponent do
</button>
</div>
<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"
id={@section_view.html_id}
data-el-heading