mirror of
https://github.com/trailbaseio/trailbase.git
synced 2026-05-02 14:39:26 -05:00
Make top navbar consistently scroll with the content on mobile and address a plathora of small mobile issues, e.g. the editor resizing.
This commit is contained in:
@@ -13,18 +13,18 @@ import { VerticalNavBar, HorizontalNavBar } from "@/components/NavBar";
|
||||
|
||||
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
||||
import { $user } from "@/lib/client";
|
||||
import { createWindowWidth } from "@/lib/signals";
|
||||
import { createIsMobile } from "@/lib/signals";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function LeftNav(props: RouteSectionProps) {
|
||||
return (
|
||||
<>
|
||||
<div class="hide-scrollbars sticky h-dvh w-[58px] overflow-y-scroll">
|
||||
<div class="hide-scrollbars sticky h-dvh w-[58px] overflow-hidden">
|
||||
<VerticalNavBar location={props.location} />
|
||||
</div>
|
||||
|
||||
<main class="absolute inset-0 left-[58px] h-dvh w-[calc(100vw-58px)] overflow-hidden">
|
||||
<main class="absolute inset-0 left-[58px] h-dvh w-[calc(100vw-58px)] overflow-x-hidden overflow-y-auto">
|
||||
<ErrorBoundary>{props.children}</ErrorBoundary>
|
||||
</main>
|
||||
</>
|
||||
@@ -34,11 +34,9 @@ function LeftNav(props: RouteSectionProps) {
|
||||
function TopNav(props: RouteSectionProps) {
|
||||
return (
|
||||
<>
|
||||
<div class="hide-scrollbars sticky h-[48px] w-screen overflow-y-scroll">
|
||||
<HorizontalNavBar location={props.location} />
|
||||
</div>
|
||||
<HorizontalNavBar height={48} location={props.location} />
|
||||
|
||||
<main class="absolute inset-0 top-[48px] h-[calc(100vh-48px)] w-screen overflow-hidden">
|
||||
<main class="max-h-[calc(100vh-48px)] w-screen">
|
||||
<ErrorBoundary>{props.children}</ErrorBoundary>
|
||||
</main>
|
||||
</>
|
||||
@@ -46,16 +44,15 @@ function TopNav(props: RouteSectionProps) {
|
||||
}
|
||||
|
||||
function WrapWithNav(props: RouteSectionProps) {
|
||||
const width = createWindowWidth();
|
||||
const showTopNav = () => width() < 680;
|
||||
const isMobile = createIsMobile();
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={showTopNav()}>
|
||||
<Match when={isMobile()}>
|
||||
<TopNav {...props} />
|
||||
</Match>
|
||||
|
||||
<Match when={!showTopNav()}>
|
||||
<Match when={!isMobile()}>
|
||||
<LeftNav {...props} />
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
@@ -109,7 +109,7 @@ type Data = {
|
||||
|
||||
function FactCard(props: { title: string; content: string; href?: string }) {
|
||||
const FCard = () => (
|
||||
<Card class="grow">
|
||||
<Card class="h-full">
|
||||
<CardContent>
|
||||
<CardTitle>{props.title}</CardTitle>
|
||||
|
||||
@@ -168,12 +168,12 @@ export function IndexPage() {
|
||||
}));
|
||||
|
||||
return (
|
||||
<div class="h-dvh overflow-y-auto">
|
||||
<div class="h-full">
|
||||
<Header title="TrailBase" />
|
||||
|
||||
<div class="prose m-4 flex grow flex-col gap-4">
|
||||
{dashboardFetch.data && (
|
||||
<div class="flex grow gap-4">
|
||||
<div class="flex shrink gap-4">
|
||||
<FactCard
|
||||
title="Users"
|
||||
content={`${dashboardFetch.data!.numUsers}`}
|
||||
|
||||
@@ -31,7 +31,7 @@ const options = [
|
||||
[`${BASE}/settings/`, TbSettings, "Settings"],
|
||||
] as const;
|
||||
|
||||
const iconSize = 22;
|
||||
const iconSize = (horizontal: boolean) => (horizontal ? 18 : 22);
|
||||
export const navBarIconStyle =
|
||||
"rounded-full transition-all p-2 hover:bg-accent-200 hover:bg-opacity-50 active:scale-90";
|
||||
export const navBarIconActiveStyle =
|
||||
@@ -41,7 +41,7 @@ function NavBarItems(props: { location: Location; horizontal: boolean }) {
|
||||
return (
|
||||
<>
|
||||
<a href={`${BASE}/`}>
|
||||
<img src={logo} width={props.horizontal ? "38" : "42"} alt="Logo" />
|
||||
<img src={logo} width={props.horizontal ? "34" : "42"} alt="Logo" />
|
||||
</a>
|
||||
|
||||
<For each={options}>
|
||||
@@ -55,7 +55,7 @@ function NavBarItems(props: { location: Location; horizontal: boolean }) {
|
||||
<TooltipTrigger as="div">
|
||||
<a href={pathname as string}>
|
||||
<div class={style()}>
|
||||
<Icon size={iconSize} />
|
||||
<Icon size={iconSize(props.horizontal)} />
|
||||
</div>
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
@@ -74,7 +74,7 @@ function NavFooter(props: { horizontal: boolean }) {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col items-center">
|
||||
<AuthButton iconSize={iconSize} />
|
||||
<AuthButton iconSize={iconSize(props.horizontal)} />
|
||||
|
||||
<Show when={!props.horizontal}>
|
||||
<div class="text-[9px]">
|
||||
@@ -85,15 +85,19 @@ function NavFooter(props: { horizontal: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function HorizontalNavBar(props: { location: Location }) {
|
||||
export function HorizontalNavBar(props: {
|
||||
height: number;
|
||||
location: Location;
|
||||
}) {
|
||||
return (
|
||||
<div class="flex h-full items-center justify-between gap-4 bg-gray-100 px-2">
|
||||
<nav class="flex h-[36px] items-center gap-4">
|
||||
<NavBarItems location={props.location} horizontal={true} />
|
||||
</nav>
|
||||
<nav
|
||||
style={{ height: `${props.height}px` }}
|
||||
class="flex w-screen items-center justify-between gap-4 bg-gray-100 p-2"
|
||||
>
|
||||
<NavBarItems location={props.location} horizontal={true} />
|
||||
|
||||
<NavFooter horizontal={true} />
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { JSXElement } from "solid-js";
|
||||
import { Match, Switch, JSX } from "solid-js";
|
||||
import { persistentAtom } from "@nanostores/persistent";
|
||||
import { useStore } from "@nanostores/solid";
|
||||
|
||||
import { createWindowWidth } from "@/lib/signals";
|
||||
import { createIsMobile } from "@/lib/signals";
|
||||
import {
|
||||
Resizable,
|
||||
ResizablePanel,
|
||||
@@ -28,13 +28,13 @@ function setSizes(next: number[]) {
|
||||
}
|
||||
|
||||
export function SplitView(props: {
|
||||
first: (props: { horizontal: boolean }) => JSXElement;
|
||||
second: (props: { horizontal: boolean }) => JSXElement;
|
||||
first: (props: { horizontal: boolean }) => JSX.Element;
|
||||
second: (props: { horizontal: boolean }) => JSX.Element;
|
||||
}) {
|
||||
function VerticalSplit() {
|
||||
return (
|
||||
<div class="hide-scrollbars flex h-full flex-col overflow-x-hidden overflow-y-scroll">
|
||||
<div>
|
||||
<div class="w-screen">
|
||||
<div class="overflow-x-auto">
|
||||
<props.first horizontal={false} />
|
||||
</div>
|
||||
|
||||
@@ -66,10 +66,17 @@ export function SplitView(props: {
|
||||
);
|
||||
}
|
||||
|
||||
const windowWidth = createWindowWidth();
|
||||
const thresh = 5 * minSizePx;
|
||||
const isMobile = createIsMobile();
|
||||
return (
|
||||
<>{windowWidth() < thresh ? <VerticalSplit /> : <HorizontalSplit />}</>
|
||||
<Switch>
|
||||
<Match when={isMobile()}>
|
||||
<VerticalSplit />
|
||||
</Match>
|
||||
|
||||
<Match when={!isMobile()}>
|
||||
<HorizontalSplit />
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { createWindowWidth } from "@/lib/signals";
|
||||
import { createIsMobile } from "@/lib/signals";
|
||||
|
||||
export function safeParseInt(v: string | undefined): number | undefined {
|
||||
if (v !== undefined) {
|
||||
@@ -224,7 +224,7 @@ export function DataTable<TData, TValue>(props: Props<TData, TValue>) {
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={local.columns.length}>
|
||||
<span>No results.</span>
|
||||
<span>Empty</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
@@ -311,7 +311,7 @@ function PaginationControl<TData>(props: {
|
||||
);
|
||||
|
||||
const PaginationInfoText = () => {
|
||||
const width = createWindowWidth();
|
||||
const isMobile = createIsMobile();
|
||||
|
||||
const pageIndex = () => table().getState().pagination.pageIndex;
|
||||
const pageCount = () => table().getPageCount();
|
||||
@@ -319,7 +319,7 @@ function PaginationControl<TData>(props: {
|
||||
|
||||
return (
|
||||
<>
|
||||
{rowCount() && width() > 578
|
||||
{rowCount() && !isMobile()
|
||||
? `page ${pageIndex() + 1} of ${pageCount()} (${rowCount()} rows total)`
|
||||
: `page ${pageIndex() + 1} of ${pageCount()}`}
|
||||
</>
|
||||
|
||||
@@ -326,7 +326,7 @@ export function AccountsPage() {
|
||||
const columns = () => buildColumns(setEditUser, refetch);
|
||||
|
||||
return (
|
||||
<div class="h-dvh overflow-y-auto">
|
||||
<div class="h-full">
|
||||
<Header
|
||||
title="Accounts"
|
||||
left={
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ErrorBoundary,
|
||||
For,
|
||||
Match,
|
||||
Show,
|
||||
Switch,
|
||||
createEffect,
|
||||
createSignal,
|
||||
@@ -32,11 +31,7 @@ import { sql, SQLConfig, SQLNamespace, SQLite } from "@codemirror/lang-sql";
|
||||
import { iconButtonStyle, IconButton } from "@/components/IconButton";
|
||||
import { Header } from "@/components/Header";
|
||||
import { SplitView } from "@/components/SplitView";
|
||||
import {
|
||||
Resizable,
|
||||
ResizablePanel,
|
||||
ResizableHandle,
|
||||
} from "@/components/ui/resizable";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Callout } from "@/components/ui/callout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -152,53 +147,51 @@ function ResultView(props: {
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={response()} fallback={<>No Data</>}>
|
||||
<Switch>
|
||||
<Match when={response()?.error}>
|
||||
Error: {response()?.error?.message}
|
||||
</Match>
|
||||
<Switch>
|
||||
<Match when={response()?.error}>
|
||||
<div class="p-4">Error: {response()?.error?.message}</div>
|
||||
</Match>
|
||||
|
||||
<Match when={(response()?.data?.columns?.length ?? 0) > 0}>
|
||||
<ErrorBoundary
|
||||
fallback={(err, _reset) => {
|
||||
return (
|
||||
<div class="m-4 flex flex-col gap-4">
|
||||
<p>Failed to render query result: {`${err}`}</p>
|
||||
<Match when={response()?.data === undefined}>
|
||||
<div class="p-4">No Data</div>
|
||||
</Match>
|
||||
|
||||
{isCached() && (
|
||||
<p>
|
||||
The view is trying to show cached data. Maybe the schema
|
||||
has changed. Try to re-execute the query.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex justify-end text-sm">
|
||||
Last executed:{" "}
|
||||
{new Date(response()?.timestamp ?? 0).toLocaleTimeString()}
|
||||
<Match when={response()?.data !== undefined}>
|
||||
<ErrorBoundary
|
||||
fallback={(err, _reset) => {
|
||||
return (
|
||||
<div class="m-4 flex flex-col gap-4">
|
||||
<p>Failed to render query result: {`${err}`}</p>
|
||||
|
||||
{isCached() && (
|
||||
<p>
|
||||
The view is trying to show cached data. Maybe the schema has
|
||||
changed. Try to re-execute the query.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* TODO: Enable pagination */}
|
||||
<DataTable
|
||||
columns={() => columnDefs(response()!.data!)}
|
||||
data={() => response()!.data!.rows as ArrayRecord[]}
|
||||
pagination={{
|
||||
pageIndex: 0,
|
||||
pageSize: 50,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<div class="flex justify-end text-sm">
|
||||
Last executed:{" "}
|
||||
{new Date(response()?.timestamp ?? 0).toLocaleTimeString()}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</Match>
|
||||
|
||||
<Match when={(response()?.data?.columns?.length ?? 0) == 0}>
|
||||
No data returned by query
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
{/* TODO: Enable pagination */}
|
||||
<DataTable
|
||||
columns={() => columnDefs(response()!.data!)}
|
||||
data={() => response()!.data!.rows as ArrayRecord[]}
|
||||
pagination={{
|
||||
pageIndex: 0,
|
||||
pageSize: 50,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -213,6 +206,7 @@ function SideBar(props: {
|
||||
const addNewScript = () => props.setSelected(createNewScript());
|
||||
|
||||
const flexStyle = () => (props.horizontal ? "flex flex-col h-dvh" : "flex");
|
||||
|
||||
return (
|
||||
<div class={`${flexStyle()} m-4 gap-2`}>
|
||||
<Button class="flex gap-2" variant="secondary" onClick={addNewScript}>
|
||||
@@ -503,61 +497,55 @@ function EditorPanel(props: {
|
||||
message="Proceeding will discard any pending changes in the current buffer. Proceed with caution."
|
||||
/>
|
||||
|
||||
<Resizable orientation="vertical" class="overflow-hidden">
|
||||
<ResizablePanel class="flex flex-col">
|
||||
<Header
|
||||
title="Editor"
|
||||
titleSelect={dirty() ? `${props.script.name}*` : props.script.name}
|
||||
left={<LeftButtons />}
|
||||
right={<HelpDialog />}
|
||||
/>
|
||||
<Header
|
||||
title="Editor"
|
||||
titleSelect={dirty() ? `${props.script.name}*` : props.script.name}
|
||||
left={<LeftButtons />}
|
||||
right={<HelpDialog />}
|
||||
/>
|
||||
|
||||
<div class="mx-4 my-2 flex grow flex-col gap-2">
|
||||
{showCallout() && (
|
||||
<Callout
|
||||
class="text-sm hover:opacity-[80%]"
|
||||
onClick={() => setShowCallout(false)}
|
||||
>
|
||||
When changing schemas, consider using migrations to consistently
|
||||
apply changes across environments. One-off alterations can
|
||||
otherwise lead to skew. Alterations using the table browser will
|
||||
produce migrations.
|
||||
</Callout>
|
||||
)}
|
||||
<div class="mx-4 my-2 flex flex-col gap-2">
|
||||
{showCallout() && (
|
||||
<Callout
|
||||
class="text-sm hover:opacity-[80%]"
|
||||
onClick={() => setShowCallout(false)}
|
||||
>
|
||||
When changing schemas, consider using migrations to consistently
|
||||
apply changes across environments. One-off alterations can otherwise
|
||||
lead to skew. Alterations using the table browser will produce
|
||||
migrations.
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
{/* Editor */}
|
||||
<div
|
||||
class="max-h-[70dvh] grow overflow-y-scroll rounded outline"
|
||||
ref={ref}
|
||||
/>
|
||||
{/* Editor */}
|
||||
<div
|
||||
class="max-h-[40dvh] shrink overflow-scroll rounded outline"
|
||||
ref={ref}
|
||||
/>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as="div">
|
||||
<Button variant="destructive" onClick={execute}>
|
||||
Execute (Ctrl+Enter)
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<div class="flex justify-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as="div">
|
||||
<Button variant="destructive" onClick={execute}>
|
||||
Execute (Ctrl+Enter)
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent>
|
||||
Execute script on the server. No turning back.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<TooltipContent>
|
||||
Execute script on the server. No turning back.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableHandle withHandle={true} />
|
||||
<Separator />
|
||||
|
||||
<ResizablePanel class="hide-scrollbars overflow-y-scroll">
|
||||
<div class="grow p-4">
|
||||
<ResultView
|
||||
script={props.script}
|
||||
response={executionResult.data ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</Resizable>
|
||||
<div class="flex shrink flex-col">
|
||||
<ResultView
|
||||
script={props.script}
|
||||
response={executionResult.data ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -634,8 +622,6 @@ export function EditorPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export default EditorPage;
|
||||
|
||||
const myTheme = EditorView.theme(
|
||||
{
|
||||
".cm-gutters": {
|
||||
@@ -693,3 +679,6 @@ const $scripts = persistentAtom<Script[]>("scripts", [defaultScript], {
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse,
|
||||
});
|
||||
|
||||
// Needed for lazy load.
|
||||
export default EditorPage;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Graph, Cell, Shape, Edge, Node } from "@antv/x6";
|
||||
import { PortManager } from "@antv/x6/lib/model/port";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { createIsMobile } from "@/lib/signals";
|
||||
|
||||
export const ER_NODE_NAME = "er-rect";
|
||||
export const LINE_HEIGHT = 24;
|
||||
@@ -125,6 +126,7 @@ export function ErdGraph(props: {
|
||||
nodes: NodeMetadata[];
|
||||
edges: EdgeMetadata[];
|
||||
}) {
|
||||
const isMobile = createIsMobile();
|
||||
let ref: HTMLDivElement | undefined;
|
||||
|
||||
onMount(() => {
|
||||
@@ -165,6 +167,7 @@ export function ErdGraph(props: {
|
||||
// v0.3.25 results in "layout is not a function": https://github.com/antvis/X6/issues/4441
|
||||
// v1.2 has completely in-compatible APIs. They'll probably need to overhaul x6 first.
|
||||
const aspect = window.innerWidth / window.innerHeight;
|
||||
|
||||
const size = Math.ceil(Math.sqrt(props.nodes.length) * aspect);
|
||||
const maxHeight = props.nodes.reduce((acc, node) => {
|
||||
const ports = node.ports;
|
||||
@@ -200,13 +203,17 @@ export function ErdGraph(props: {
|
||||
graph.zoomToFit({ padding: 100, maxScale: 1 });
|
||||
});
|
||||
|
||||
const style = () => {
|
||||
if (isMobile()) {
|
||||
return "h-[calc(100dvh-120px)] w-[calc(100dvw)] overflow-clip";
|
||||
}
|
||||
return "h-[calc(100dvh-65px)] w-[calc(100dvw-58px)] overflow-clip";
|
||||
};
|
||||
|
||||
// NOTE: The double sytling is somehow needed otherwise it overflows on mobile. x6 may also apply some styles on top.
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
class={cn(
|
||||
"h-[calc(100dvh-66px)] w-[calc(100dvw-58px)] overflow-clip",
|
||||
props.class,
|
||||
)}
|
||||
/>
|
||||
<div class={style()}>
|
||||
<div ref={ref} class={cn(style(), props.class)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -172,6 +172,12 @@ function SchemaErdGraph(props: { schema: ListSchemasResponse }) {
|
||||
return { nodes, edges };
|
||||
});
|
||||
|
||||
{
|
||||
/*
|
||||
<ErdGraph nodes={nodesAndEdges().nodes} edges={nodesAndEdges().edges} />
|
||||
*/
|
||||
}
|
||||
|
||||
return (
|
||||
<ErdGraph nodes={nodesAndEdges().nodes} edges={nodesAndEdges().edges} />
|
||||
);
|
||||
@@ -181,7 +187,7 @@ export function ErdPage() {
|
||||
const schemaFetch = createTableSchemaQuery();
|
||||
|
||||
return (
|
||||
<div class="h-dvh">
|
||||
<div class="flex h-full flex-col">
|
||||
<Header title="Schema" />
|
||||
|
||||
<Switch>
|
||||
|
||||
@@ -209,7 +209,7 @@ export function LogsPage() {
|
||||
const [showGeoipDialog, setShowGeoipDialog] = createSignal(false);
|
||||
|
||||
return (
|
||||
<div class="h-dvh overflow-y-auto">
|
||||
<div class="h-full">
|
||||
<Header
|
||||
title="Logs"
|
||||
left={
|
||||
@@ -268,18 +268,18 @@ export function LogsPage() {
|
||||
|
||||
<Match when={logsFetch.data}>
|
||||
{pagination().pageIndex === 0 && logsFetch.data!.stats && (
|
||||
<div class="mb-4 flex h-[300px] w-full gap-4">
|
||||
<div class={showMap() ? "w-1/2 grow" : "w-full"}>
|
||||
<LogsChart stats={logsFetch.data!.stats!} />
|
||||
</div>
|
||||
|
||||
{showMap() && logsFetch.data!.stats!.country_codes && (
|
||||
<div class="flex w-1/2 max-w-[500px] items-center">
|
||||
<div class="mb-4 flex w-full flex-col gap-4 md:h-[300px] md:flex-row">
|
||||
<Show when={showMap() && logsFetch.data!.stats!.country_codes}>
|
||||
<div class="flex items-center md:w-1/2 md:max-w-[500px]">
|
||||
<WorldMap
|
||||
country_codes={logsFetch.data!.stats!.country_codes!}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<div class={showMap() ? "md:w-1/2" : "w-full"}>
|
||||
<LogsChart stats={logsFetch.data!.stats!} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ export function EmailSettings(props: {
|
||||
>
|
||||
{buildOptionalTextFormField({
|
||||
label: textLabel("Username"),
|
||||
autocomplete: "username",
|
||||
autocomplete: "off",
|
||||
})}
|
||||
</form.Field>
|
||||
|
||||
@@ -232,7 +232,7 @@ export function EmailSettings(props: {
|
||||
// NOTE: we're not using buildSecretFormField here because it doesn't support optional.
|
||||
buildOptionalTextFormField({
|
||||
type: "password",
|
||||
autocomplete: "current-password",
|
||||
autocomplete: "off",
|
||||
label: textLabel("Password"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -579,7 +579,7 @@ function ArrayRecordTable(props: {
|
||||
placeholder={`Filter Query, e.g. '(col0 > 5 && col0 < 20) || col1 = "val"'`}
|
||||
/>
|
||||
|
||||
<div class="overflow-auto pt-4">
|
||||
<div class="overflow-x-auto pt-4">
|
||||
<DataTable
|
||||
// NOTE: The formatting is done via the columnsDefs.
|
||||
columns={columnDefs}
|
||||
@@ -908,7 +908,7 @@ export function TablePane(props: {
|
||||
/>
|
||||
</SheetContent>
|
||||
|
||||
<div class="space-y-2.5 overflow-auto">
|
||||
<div class="space-y-2.5 overflow-x-auto">
|
||||
<DataTable
|
||||
columns={() => indexColumns}
|
||||
data={indexes}
|
||||
|
||||
@@ -82,7 +82,7 @@ function TablePickerPane(props: {
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`${horizontal() ? "flex h-dvh flex-col" : "flex"} hide-scrollbars gap-2 overflow-scroll p-4`}
|
||||
class={`${horizontal() ? "flex h-dvh flex-col" : "flex"} gap-2 overflow-scroll p-4`}
|
||||
>
|
||||
<SwitchToggle
|
||||
class="flex items-center justify-center gap-2"
|
||||
|
||||
@@ -12,6 +12,13 @@ export function createWindowWidth(): Accessor<number> {
|
||||
return width;
|
||||
}
|
||||
|
||||
const mobileBreakpoint = 768;
|
||||
|
||||
export function createIsMobile(): Accessor<boolean> {
|
||||
const width = createWindowWidth();
|
||||
return () => width() < mobileBreakpoint;
|
||||
}
|
||||
|
||||
export function createSetOnce<T>(initial: T): [
|
||||
() => T,
|
||||
(v: T) => void,
|
||||
|
||||
Reference in New Issue
Block a user