Skip to content

Commit

Permalink
Improve result formatting setting (#458)
Browse files Browse the repository at this point in the history
  • Loading branch information
macjuul authored Oct 17, 2024
1 parent 7506e71 commit 9f1822c
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 74 deletions.
12 changes: 3 additions & 9 deletions src/components/App/settings/tabs/Appearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
DESIGNER_NODE_MODES,
LINE_STYLES,
ORIENTATIONS,
RESULT_FORMATS,
RESULT_MODES,
SIDEBAR_MODES,
THEMES,
VALUE_MODES,
} from "~/constants";

import { Box, Checkbox, Select, Slider } from "@mantine/core";
Expand All @@ -28,14 +28,14 @@ export function AppearanceTab() {
// const [resultWordWrap, setResultWordWrap] = useSetting(CAT, "resultWordWrap");
const [defaultResultMode, setDefaultResultMode] = useSetting(CAT, "defaultResultMode");
const [queryOrientation, setQueryOrientation] = useSetting(CAT, "queryOrientation");
const [valueMode, setValueMode] = useSetting(CAT, "valueMode");
const [defaultDiagramMode, setDefaultDiagramMode] = useSetting(CAT, "defaultDiagramMode");
const [sidebarMode, setSidebarMode] = useSetting(CAT, "sidebarMode");
const [lineStyle, setLineStyle] = useSetting(CAT, "lineStyle");
const [defaultDiagramMode, setDefaultDiagramMode] = useSetting(CAT, "defaultDiagramMode");
const [defaultDiagramDirection, setDefaultDiagramDirection] = useSetting(
CAT,
"defaultDiagramDirection",
);

const [defaultDiagramShowLinks, setDefaultDiagramShowLinks] = useSetting(
CAT,
"defaultDiagramShowLinks",
Expand All @@ -62,12 +62,6 @@ export function AppearanceTab() {
value={sidebarMode}
onChange={setSidebarMode as any}
/>
<Select
label="Value formatting mode"
data={VALUE_MODES}
value={valueMode}
onChange={setValueMode as any}
/>
</SettingsSection>

<SettingsSection label="Scale">
Expand Down
69 changes: 69 additions & 0 deletions src/components/ListMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Text, Tooltip } from "@mantine/core";
import { Menu } from "@mantine/core";
import type { PropsWithChildren } from "react";
import { useIsLight } from "~/hooks/theme";
import type { Listable } from "~/types";
import { iconCheck } from "~/util/icons";
import { Icon } from "../Icon";

export interface ListMenuProps<T extends string> {
data: Listable<T>[];
value: T;
onChange: (value: T) => void;
}

export function ListMenu<T extends string>({
data,
value,
onChange,
children,
}: PropsWithChildren<ListMenuProps<T>>) {
const isLight = useIsLight();

return (
<Menu
position="bottom-end"
transitionProps={{
transition: "scale-y",
}}
>
<Menu.Target>
{children}
</Menu.Target>
<Menu.Dropdown w={264}>
{data.map(({ label, value: itemValue, icon, description }) => (
<Menu.Item
key={itemValue}
onClick={() => onChange(itemValue)}
leftSection={
icon && (
<Icon
path={icon}
mr="xs"
/>
)
}
rightSection={
value === itemValue && (
<Icon
path={iconCheck}
ml="xs"
/>
)
}
>
<Text c="bright">{label}</Text>
{description && (
<Text
c={isLight ? "slate.5" : "slate.3"}
size="sm"
>
{description}
</Text>
)}
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
);
}
52 changes: 42 additions & 10 deletions src/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import type {
Listable,
Orientation,
Protocol,
ResultFormat,
ResultMode,
SchemaMode,
Selectable,
SidebarMode,
ValueMode,
ViewInfo,
ViewMode,
} from "./types";
Expand All @@ -36,11 +36,13 @@ import {
iconAPI,
iconAccount,
iconAuth,
iconBraces,
iconCloud,
iconCog,
iconCombined,
iconCreditCard,
iconDataTable,
iconDatabase,
iconDesigner,
iconEmail,
iconExplorer,
Expand Down Expand Up @@ -80,10 +82,45 @@ export const THEMES = [
];

export const RESULT_MODES: Listable<ResultMode>[] = [
{ label: "Combined", value: "combined", icon: iconCombined },
{ label: "Individual", value: "single", icon: iconQuery },
{ label: "Table", value: "table", icon: iconDataTable },
{ label: "Live", value: "live", icon: iconLive },
{
label: "Combined",
value: "combined",
icon: iconCombined,
description: "View all results in a single list",
},
{
label: "Individual",
value: "single",
icon: iconQuery,
description: "Inspect each result individually",
},
{
label: "Table",
value: "table",
icon: iconDataTable,
description: "Render query results in a table",
},
{
label: "Live",
value: "live",
icon: iconLive,
description: "Subscribe to live query results",
},
];

export const RESULT_FORMATS: Listable<ResultFormat>[] = [
{
label: "SurrealQL",
value: "sql",
icon: iconDatabase,
description: "Format results in full SurrealQL",
},
{
label: "JSON",
value: "json",
icon: iconBraces,
description: "Format results in classic JSON",
},
];

export const CONNECTION_PROTOCOLS: ProtocolOption[] = [
Expand Down Expand Up @@ -116,11 +153,6 @@ export const CODE_LANGUAGES: Selectable<CodeLang>[] = [
{ label: "PHP", value: "php" },
];

export const VALUE_MODES: Selectable<ValueMode>[] = [
{ label: "JSON", value: "json" },
{ label: "SurrealQL", value: "sql" },
];

export const SIDEBAR_MODES: Selectable<SidebarMode>[] = [
{ label: "Expandable", value: "expandable" },
{ label: "Compact", value: "compact" },
Expand Down
15 changes: 8 additions & 7 deletions src/hooks/surrealql.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { useDebouncedValue } from "@mantine/hooks";
import { useMemo } from "react";
import type { ValueMode } from "~/types";
import type { ResultFormat } from "~/types";
import { formatValue, parseValue } from "~/util/surrealql";
import { useSetting } from "./config";
import { useActiveQuery } from "./connection";
import { useStable } from "./stable";

export type Formatter = (value: any) => string;

/**
* A hook used to format SurrealQL structures into strings
*/
export function useValueFormatter(): [Formatter, ValueMode] {
const [mode] = useSetting("appearance", "valueMode");
export function useResultFormatter(): [Formatter, ResultFormat] {
const query = useActiveQuery();
const format = query?.resultFormat || "sql";

const format = useStable((value: any) => {
return formatValue(value, mode === "json", true);
const formatter = useStable((value: any) => {
return formatValue(value, format === "json", true);
});

return [format, mode];
return [formatter, format];
}

/**
Expand Down
70 changes: 49 additions & 21 deletions src/screens/database/views/query/ResultPane/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,33 @@ import {
Center,
Divider,
Group,
Menu,
Pagination,
Stack,
Text,
Tooltip,
UnstyledButton,
} from "@mantine/core";

import { iconBroadcastOff, iconCursor, iconHelp, iconLive, iconQuery } from "~/util/icons";

import type { SelectionRange } from "@codemirror/state";
import type { EditorView } from "@codemirror/view";
import { useState } from "react";
import { useLayoutEffect } from "react";
import { isMini } from "~/adapter";
import { DataTable } from "~/components/DataTable";
import { Icon } from "~/components/Icon";
import { ListMenu } from "~/components/ListMenu";
import { ContentPane } from "~/components/Pane";
import { RESULT_MODES } from "~/constants";
import { RESULT_FORMATS, RESULT_MODES } from "~/constants";
import { executeEditorQuery } from "~/editor/query";
import { useStable } from "~/hooks/stable";
import { useIsLight } from "~/hooks/theme";
import { cancelLiveQueries } from "~/screens/database/connection/connection";
import { useConfigStore } from "~/stores/config";
import { useDatabaseStore } from "~/stores/database";
import { useInterfaceStore } from "~/stores/interface";
import type { QueryResponse, ResultMode, TabQuery } from "~/types";
import { iconBroadcastOff, iconCursor, iconHelp, iconLive, iconQuery } from "~/util/icons";
import type { QueryResponse, ResultFormat, ResultMode, TabQuery } from "~/types";
import { CombinedJsonPreview, LivePreview, SingleJsonPreview } from "./preview";

function computeRowCount(response: QueryResponse) {
Expand Down Expand Up @@ -72,6 +73,7 @@ export function ResultPane({
const isLight = useIsLight();
const [resultTab, setResultTab] = useState<number>(1);
const resultMode = activeTab.resultMode;
const resultFormat = activeTab.resultFormat;
const responses = responseMap[activeTab.id] || [];
const activeResponse = responses[resultTab - 1];

Expand Down Expand Up @@ -102,12 +104,20 @@ export function ResultPane({
});
};

const setResultFormat = (format: ResultFormat) => {
updateQueryTab({
id: activeTab.id,
resultFormat: format,
});
};

// biome-ignore lint/correctness/useExhaustiveDependencies: Reset result tab when responses change
useLayoutEffect(() => {
setResultTab(1);
}, [responses.length]);

const activeMode = RESULT_MODES.find((r) => r.value === resultMode);
const activeFormat = RESULT_FORMATS.find((r) => r.value === resultFormat);
const hasSelection = selection?.empty === false;

const statusText = showResponses
Expand Down Expand Up @@ -156,16 +166,43 @@ export function ResultPane({
</Text>
)}

<Menu>
<Menu.Target>
{!isMini && (
<ListMenu
data={RESULT_FORMATS}
value={resultFormat}
onChange={setResultFormat}
>
<Tooltip label="Change result format">
<Button
size="xs"
radius="xs"
aria-label="Change format mode"
variant="light"
color="slate"
leftSection={
activeFormat?.icon && <Icon path={activeFormat.icon} />
}
>
{activeFormat?.label ?? resultFormat}
</Button>
</Tooltip>
</ListMenu>
)}

<ListMenu
data={RESULT_MODES}
value={resultMode}
onChange={setResultMode}
>
<Tooltip label="Change result mode">
{isMini ? (
<Tooltip label="Click to change mode">
<ActionIcon
aria-label={`Change result mode. Currently ${activeMode}`}
h={30}
w={30}
>
<Icon path={activeMode ? activeMode.icon : iconHelp} />
<Icon path={activeMode?.icon ?? iconHelp} />
</ActionIcon>
</Tooltip>
) : (
Expand All @@ -175,24 +212,15 @@ export function ResultPane({
aria-label="Change result mode"
variant="light"
color="slate"
leftSection={activeMode && <Icon path={activeMode.icon} />}
leftSection={
activeMode && <Icon path={activeMode?.icon ?? iconHelp} />
}
>
{activeMode?.label ?? "Unknown"}
</Button>
)}
</Menu.Target>
<Menu.Dropdown>
{RESULT_MODES.map(({ label, value, icon }) => (
<Menu.Item
key={value}
onClick={() => setResultMode(value)}
leftSection={<Icon path={icon} />}
>
{label}
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
</Tooltip>
</ListMenu>

<Button
size="xs"
Expand Down
Loading

0 comments on commit 9f1822c

Please sign in to comment.