mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-12 14:36:20 +08:00
Mobile quality-of-life (part 1) (#2013)
This commit is contained in:
parent
4899767673
commit
e1ac442c8c
10 changed files with 130 additions and 50 deletions
|
@ -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%;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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}`,
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -14,43 +14,39 @@ 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">
|
<.menu id={"cell-#{@id}-insert"} position={:bottom_left} distant>
|
||||||
<div class="absolute -left-1 top-0 bottom-0 flex items-center transform -translate-x-full">
|
<:toggle>
|
||||||
<.menu id={"cell-#{@id}-insert"} position={:bottom_left} distant>
|
<button class="button-base button-small flex items-center pr-1">
|
||||||
<:toggle>
|
<div
|
||||||
<button class="button-base button-small flex items-center pr-1">
|
class="pr-2"
|
||||||
<div
|
phx-click="insert_cell_below"
|
||||||
class="pr-2"
|
phx-value-type="code"
|
||||||
phx-click="insert_cell_below"
|
phx-value-section_id={@section_id}
|
||||||
phx-value-type="code"
|
phx-value-cell_id={@cell_id}
|
||||||
phx-value-section_id={@section_id}
|
phx-value-language="elixir"
|
||||||
phx-value-cell_id={@cell_id}
|
>
|
||||||
phx-value-language="elixir"
|
+ <%= @default_language |> Atom.to_string() |> String.capitalize() %>
|
||||||
>
|
</div>
|
||||||
+ <%= @default_language |> Atom.to_string() |> String.capitalize() %>
|
<div class="pl-1 flex items-center border-l border-gray-200">
|
||||||
</div>
|
<.remix_icon icon="arrow-down-s-line" class="text-lg leading-none" />
|
||||||
<div class="pl-1 flex items-center border-l border-gray-200">
|
</div>
|
||||||
<.remix_icon icon="arrow-down-s-line" class="text-lg leading-none" />
|
</button>
|
||||||
</div>
|
</:toggle>
|
||||||
</button>
|
<.menu_item>
|
||||||
</:toggle>
|
<button role="menuitem" phx-click="set_default_language" phx-value-language="elixir">
|
||||||
<.menu_item>
|
<.cell_icon cell_type={:code} language={:elixir} />
|
||||||
<button role="menuitem" phx-click="set_default_language" phx-value-language="elixir">
|
<span>Elixir</span>
|
||||||
<.cell_icon cell_type={:code} language={:elixir} />
|
</button>
|
||||||
<span>Elixir</span>
|
</.menu_item>
|
||||||
</button>
|
<.menu_item>
|
||||||
</.menu_item>
|
<button role="menuitem" phx-click="set_default_language" phx-value-language="erlang">
|
||||||
<.menu_item>
|
<.cell_icon cell_type={:code} language={:erlang} />
|
||||||
<button role="menuitem" phx-click="set_default_language" phx-value-language="erlang">
|
<span>Erlang</span>
|
||||||
<.cell_icon cell_type={:code} language={:erlang} />
|
</button>
|
||||||
<span>Erlang</span>
|
</.menu_item>
|
||||||
</button>
|
</.menu>
|
||||||
</.menu_item>
|
|
||||||
</.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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue