Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/webapp/app/components/Shortcuts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ function ShortcutContent() {
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Jump to next/previous run">
<ShortcutKey shortcut={{ key: "[" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "]" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "j" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "k" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Expand all">
<ShortcutKey shortcut={{ key: "e" }} variant="medium/bright" />
Expand Down
30 changes: 30 additions & 0 deletions apps/webapp/app/components/TimezoneSetter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useFetcher } from "@remix-run/react";
import { useEffect, useRef } from "react";
import { useTypedLoaderData } from "remix-typedjson";
import type { loader } from "~/root";

export function TimezoneSetter() {
const { timezone: storedTimezone } = useTypedLoaderData<typeof loader>();
const fetcher = useFetcher();
const hasSetTimezone = useRef(false);

useEffect(() => {
if (hasSetTimezone.current) return;

const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;

if (browserTimezone && browserTimezone !== storedTimezone) {
hasSetTimezone.current = true;
fetcher.submit(
{ timezone: browserTimezone },
{
method: "POST",
action: "/resources/timezone",
encType: "application/json",
}
);
}
}, [storedTimezone, fetcher]);

return null;
}
58 changes: 57 additions & 1 deletion apps/webapp/app/components/code/TSQLEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { sql, StandardSQL } from "@codemirror/lang-sql";
import { autocompletion, startCompletion } from "@codemirror/autocomplete";
import { linter, lintGutter } from "@codemirror/lint";
import { EditorView } from "@codemirror/view";
import { EditorView, keymap } from "@codemirror/view";
import type { ViewUpdate } from "@codemirror/view";
import { CheckIcon, ClipboardIcon, SparklesIcon, TrashIcon } from "@heroicons/react/20/solid";
import {
Expand Down Expand Up @@ -60,6 +60,51 @@ const defaultProps: TSQLEditorDefaultProps = {
schema: [],
};

// Toggle comment on current line or selected lines with -- comment symbol
const toggleLineComment = (view: EditorView): boolean => {
const { from, to } = view.state.selection.main;
const startLine = view.state.doc.lineAt(from);
const endLine = view.state.doc.lineAt(to);

// Collect all lines in the selection
const lines: { from: number; to: number; text: string }[] = [];
for (let i = startLine.number; i <= endLine.number; i++) {
const line = view.state.doc.line(i);
lines.push({ from: line.from, to: line.to, text: line.text });
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Determine action: if all non-empty lines are commented, uncomment; otherwise comment
const allCommented = lines.every((line) => {
const trimmed = line.text.trimStart();
return trimmed.length === 0 || trimmed.startsWith("--");
});

const changes = lines
.map((line) => {
const trimmed = line.text.trimStart();
if (trimmed.length === 0) return null; // skip empty lines
const indent = line.text.length - trimmed.length;

if (allCommented) {
// Remove comment: strip "-- " or just "--"
const afterComment = trimmed.slice(2);
const newText = line.text.slice(0, indent) + afterComment.replace(/^\s/, "");
return { from: line.from, to: line.to, insert: newText };
} else {
// Add comment: prepend "-- " to the line content
const newText = line.text.slice(0, indent) + "-- " + trimmed;
return { from: line.from, to: line.to, insert: newText };
}
})
.filter((c): c is { from: number; to: number; insert: string } => c !== null);

if (changes.length > 0) {
view.dispatch({ changes });
}

return true;
};

export function TSQLEditor(opts: TSQLEditorProps) {
const {
defaultValue = "",
Expand Down Expand Up @@ -133,6 +178,14 @@ export function TSQLEditor(opts: TSQLEditorProps) {
);
}

// Add keyboard shortcut for toggling comments
exts.push(
keymap.of([
{ key: "Cmd-/", run: toggleLineComment },
{ key: "Ctrl-/", run: toggleLineComment },
])
);

return exts;
}, [schema, linterEnabled]);

Expand Down Expand Up @@ -218,6 +271,9 @@ export function TSQLEditor(opts: TSQLEditorProps) {
"min-h-0 flex-1 overflow-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
)}
ref={editor}
onClick={() => {
view?.focus();
}}
onBlur={() => {
if (!onBlur) return;
if (!view) return;
Expand Down
13 changes: 11 additions & 2 deletions apps/webapp/app/components/code/codeMirrorSetup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { closeBrackets } from "@codemirror/autocomplete";
import { indentWithTab } from "@codemirror/commands";
import { indentWithTab, history, historyKeymap, undo, redo } from "@codemirror/commands";
import { bracketMatching } from "@codemirror/language";
import { lintKeymap } from "@codemirror/lint";
import { highlightSelectionMatches } from "@codemirror/search";
Expand All @@ -18,6 +18,7 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
const options = [
drawSelection(),
dropCursor(),
history(),
bracketMatching(),
closeBrackets(),
Prec.highest(
Expand All @@ -31,7 +32,15 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
},
])
),
keymap.of([indentWithTab, ...lintKeymap]),
// Explicit undo/redo keybindings with high precedence
Prec.high(
keymap.of([
{ key: "Mod-z", run: undo },
{ key: "Mod-Shift-z", run: redo },
{ key: "Mod-y", run: redo },
])
),
keymap.of([indentWithTab, ...historyKeymap, ...lintKeymap]),
];

if (showLineNumbers) {
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/logs/LogDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
import { useTypedFetcher } from "remix-typedjson";
import { cn } from "~/utils/cn";
import { Button } from "~/components/primitives/Buttons";
import { DateTime } from "~/components/primitives/DateTime";
import { DateTimeAccurate } from "~/components/primitives/DateTime";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Spinner } from "~/components/primitives/Spinner";
Expand Down Expand Up @@ -234,7 +234,7 @@ function DetailsTab({ log, runPath, searchTerm }: { log: LogEntry; runPath: stri
<div className="mb-6">
<Header3 className="mb-2">Timestamp</Header3>
<div className="text-sm text-text-dimmed">
<DateTime date={log.startTime} />
<DateTimeAccurate date={log.startTime} />
</div>
</div>

Expand Down
30 changes: 18 additions & 12 deletions apps/webapp/app/components/logs/LogsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
import { Link } from "@remix-run/react";
import { useEffect, useRef, useState } from "react";
import { cn } from "~/utils/cn";
import { Button } from "~/components/primitives/Buttons";
Expand All @@ -8,7 +9,7 @@ import { useProject } from "~/hooks/useProject";
import type { LogEntry } from "~/presenters/v3/LogsListPresenter.server";
import { getLevelColor, highlightSearchText } from "~/utils/logUtils";
import { v3RunSpanPath } from "~/utils/pathBuilder";
import { DateTime } from "../primitives/DateTime";
import { DateTimeAccurate } from "../primitives/DateTime";
import { Paragraph } from "../primitives/Paragraph";
import { Spinner } from "../primitives/Spinner";
import { TruncatedCopyableValue } from "../primitives/TruncatedCopyableValue";
Expand All @@ -24,8 +25,6 @@ import {
TableRow,
type TableVariant,
} from "../primitives/Table";
import { PopoverMenuItem } from "~/components/primitives/Popover";
import { Link } from "@remix-run/react";

type LogsTableProps = {
logs: LogEntry[];
Expand All @@ -34,6 +33,7 @@ type LogsTableProps = {
isLoadingMore?: boolean;
hasMore?: boolean;
onLoadMore?: () => void;
onCheckForMore?: () => void;
variant?: TableVariant;
selectedLogId?: string;
onLogSelect?: (logId: string) => void;
Expand Down Expand Up @@ -63,6 +63,7 @@ export function LogsTable({
isLoadingMore = false,
hasMore = false,
onLoadMore,
onCheckForMore,
selectedLogId,
onLogSelect,
}: LogsTableProps) {
Expand Down Expand Up @@ -161,7 +162,7 @@ export function LogsTable({
boxShadow: getLevelBoxShadow(log.level),
}}
>
<DateTime date={log.startTime} />
<DateTimeAccurate date={log.startTime} />
</TableCell>
<TableCell className="min-w-24">
<TruncatedCopyableValue value={log.runId} />
Expand Down Expand Up @@ -203,21 +204,26 @@ export function LogsTable({
{/* Infinite scroll trigger */}
{hasMore && logs.length > 0 && (
<div ref={loadMoreRef} className="flex items-center justify-center py-12">
<div
className={cn(
"flex items-center gap-2",
!showLoadMoreSpinner && "invisible"
)}
>
<div className={cn("flex items-center gap-2", !showLoadMoreSpinner && "invisible")}>
<Spinner /> <span className="text-text-dimmed">Loading more…</span>
</div>
</div>
)}
{/* Show all logs message */}
{/* Show all logs message with check for more button */}
{!hasMore && logs.length > 0 && (
<div className="flex items-center justify-center py-12">
<div className="flex items-center gap-2">
<div className="flex flex-col items-center gap-3">
<span className="text-text-dimmed">Showing all {logs.length} logs</span>
{onCheckForMore && (
<Button
LeadingIcon={ArrowPathIcon}
variant="tertiary/small"
onClick={onCheckForMore}
disabled={isLoadingMore}
>
{isLoadingMore ? "Checking…" : "Check for new logs"}
</Button>
)}
</div>
</div>
Comment thread
mpcgrid marked this conversation as resolved.
)}
Expand Down
Loading