Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Hover Focus Dimming Feature for Designer View #758

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all 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
8 changes: 8 additions & 0 deletions src/constants.tsx
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import type {
Dataset,
DiagramAlgorithm,
DiagramDirection,
DiagramHoverFocus,
DiagramLineStyle,
DiagramLinks,
DiagramMode,
@@ -380,6 +381,13 @@ export const DESIGNER_ALGORITHMS: Selectable<DiagramAlgorithm>[] = [
{ label: "Spaced", value: "spaced" },
];

export const DESIGNER_HOVER_FOCUS: Selectable<DiagramHoverFocus>[] = [
{ label: "Default", value: "default" },
{ label: "None", value: "none" },
{ label: "Dim", value: "dim" },
{ label: "Dim recursive", value: "recursive" },
];

export const SCHEMA_MODES: Selectable<SchemaMode>[] = [
{ label: "Schemaless", value: "schemaless" },
{ label: "Schemafull", value: "schemafull" },
154 changes: 154 additions & 0 deletions src/screens/surrealist/views/designer/TableGraphPane/index.tsx
Original file line number Diff line number Diff line change
@@ -63,6 +63,7 @@ import {
import {
DESIGNER_ALGORITHMS,
DESIGNER_DIRECTIONS,
DESIGNER_HOVER_FOCUS,
DESIGNER_LINE_STYLES,
DESIGNER_LINKS,
DESIGNER_NODE_MODES,
@@ -71,6 +72,7 @@ import {
import type {
DiagramAlgorithm,
DiagramDirection,
DiagramHoverFocus,
DiagramLineStyle,
DiagramLinks,
DiagramMode,
@@ -120,6 +122,7 @@ export function TableGraphPane(props: TableGraphPaneProps) {
diagramLineStyle,
diagramLinkMode,
diagramMode,
diagramHoverFocus,
] = useConnection((c) => [
c?.id ?? "",
c?.designerTableList,
@@ -128,6 +131,7 @@ export function TableGraphPane(props: TableGraphPaneProps) {
c?.diagramLineStyle,
c?.diagramLinkMode,
c?.diagramMode,
c?.diagramHoverFocus,
]);

const [isExporting, setIsExporting] = useState(false);
@@ -144,17 +148,22 @@ export function TableGraphPane(props: TableGraphPaneProps) {
const nodesInitialized = useNodesInitialized({ includeHiddenNodes: true });
const isLayedOut = useRef(false);

const [hoveredNode, setHoveredNode] = useState<string | null>(null);
const [isDragging, setIsDragging] = useState(false);

const [defaultAlgorithm] = useSetting("appearance", "defaultDiagramAlgorithm");
const [defaultDirection] = useSetting("appearance", "defaultDiagramDirection");
const [defaultLineStyle] = useSetting("appearance", "defaultDiagramLineStyle");
const [defaultLinkMode] = useSetting("appearance", "defaultDiagramLinkMode");
const [defaultNodeMode] = useSetting("appearance", "defaultDiagramMode");
const [defaultHoverFocus] = useSetting("appearance", "defaultDiagramHoverFocus");

const algorithm = applyDefault(diagramAlgorithm, defaultAlgorithm);
const direction = applyDefault(diagramDirection, defaultDirection);
const lineStyle = applyDefault(diagramLineStyle, defaultLineStyle);
const linkMode = applyDefault(diagramLinkMode, defaultLinkMode);
const nodeMode = applyDefault(diagramMode, defaultNodeMode);
const hoverFocus = applyDefault(diagramHoverFocus, defaultHoverFocus);

useLayoutEffect(() => {
if (isLayedOut.current || !nodesInitialized) {
@@ -216,6 +225,24 @@ export function TableGraphPane(props: TableGraphPaneProps) {
};
}),
);

setIsDragging(true);
});

const handleNodeDragStop = useStable((_: MouseEvent, node: Node) => {
setIsDragging(false);
});

const handleNodeMouseEnter = useStable((_: MouseEvent, node: Node) => {
if (hoverFocus === "dim" || hoverFocus === "recursive") {
setHoveredNode(node.id);
}
});

const handleNodeMouseLeave = useStable(() => {
if (hoverFocus === "dim" || hoverFocus === "recursive") {
setHoveredNode(null);
}
});

const saveImage = useStable(async (type: "png" | "svg") => {
@@ -298,6 +325,13 @@ export function TableGraphPane(props: TableGraphPaneProps) {
});
});

const setDiagramHoverFocus = useStable((mode: string) => {
updateConnection({
id: connectionId,
diagramHoverFocus: mode as DiagramHoverFocus,
});
});

const handleZoomIn = useStable(() => {
zoomIn({ duration: 150 });
});
@@ -339,6 +373,116 @@ export function TableGraphPane(props: TableGraphPaneProps) {
});
}, [props.active]);

useEffect(() => {
const shouldApplyDimming =
(hoverFocus === "dim" || hoverFocus === "recursive") &&
hoveredNode !== null &&
!isDragging;

if (!shouldApplyDimming) {
setNodes((nodes) =>
nodes.map((node) => ({
...node,
className: node.className
? node.className
.split(" ")
.filter((c) => c !== "dimmed")
.join(" ")
: undefined,
})),
);

setEdges((edges) =>
edges.map((edge) => ({
...edge,
className: edge.className
? edge.className
.split(" ")
.filter((c) => c !== "dimmed")
.join(" ")
: edge.type === "elk"
? "record-link"
: undefined,
})),
);

return;
}

const relatedNodes = new Set<string>([hoveredNode]);

if (hoverFocus === "recursive") {
const traverseGraph = (nodeId: string, visited: Set<string>, isForward: boolean) => {
if (visited.has(nodeId)) return;

visited.add(nodeId);
relatedNodes.add(nodeId);

for (const edge of edges) {
if (isForward && edge.source === nodeId && !visited.has(edge.target)) {
traverseGraph(edge.target, visited, true);
}
if (!isForward && edge.target === nodeId && !visited.has(edge.source)) {
traverseGraph(edge.source, visited, false);
}
}
};

traverseGraph(hoveredNode, new Set<string>(), true);
traverseGraph(hoveredNode, new Set<string>(), false);
} else {
for (const edge of edges) {
if (edge.source === hoveredNode) {
relatedNodes.add(edge.target);
}
if (edge.target === hoveredNode) {
relatedNodes.add(edge.source);
}
}
}

setNodes((nodes) =>
nodes.map((node) => {
const isRelated = relatedNodes.has(node.id);
const classes = node.className
? node.className.split(" ").filter((c) => c !== "dimmed")
: [];

if (!isRelated) {
classes.push("dimmed");
}

return {
...node,
className: classes.length > 0 ? classes.join(" ") : undefined,
};
}),
);

setEdges((edges) =>
edges.map((edge) => {
const isRelated = relatedNodes.has(edge.source) && relatedNodes.has(edge.target);

const baseClasses = edge.className
? edge.className.split(" ").filter((c) => c !== "dimmed")
: [];

if (edge.type === "elk" && baseClasses.length === 0) {
baseClasses.push("record-link");
}

if (!isRelated) {
baseClasses.push("dimmed");
}

return {
...edge,
className: baseClasses.join(" "),
};
}),
);
}, [hoveredNode, hoverFocus, edges, isDragging]);

useIntent("focus-table", ({ table }) => {
const node = getNodes().find((node) => node.id === table);

@@ -468,6 +612,13 @@ export function TableGraphPane(props: TableGraphPaneProps) {
onChange={setDiagramLinkMode as any}
comboboxProps={{ withinPortal: false }}
/>
<Label>Hover focus</Label>
<Select
data={DESIGNER_HOVER_FOCUS}
value={diagramHoverFocus}
onChange={setDiagramHoverFocus as any}
comboboxProps={{ withinPortal: false }}
/>
</SimpleGrid>
<Text fz="sm">
You can customise default values in the settings menu
@@ -507,6 +658,9 @@ export function TableGraphPane(props: TableGraphPaneProps) {
}}
onNodeClick={handleNodeClick}
onNodeDragStart={handleNodeDragStart}
onNodeDragStop={handleNodeDragStop}
onNodeMouseEnter={handleNodeMouseEnter}
onNodeMouseLeave={handleNodeMouseLeave}
onContextMenu={showContextMenu([
{
key: "create",
Original file line number Diff line number Diff line change
@@ -68,6 +68,19 @@
stroke-dasharray: 5;
}

:global(.react-flow__node.dimmed),
:global(.react-flow__edge.dimmed) :global(.react-flow__edge-text),
:global(.react-flow__edge.dimmed)>path {
opacity: 0.2;
transition: opacity 0.2s ease-in-out;
}

:global(.react-flow__edge.dimmed) :global(.react-flow__edge-textbg) {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}


@include light {

:global(.react-flow__edge-textbg) {
3 changes: 3 additions & 0 deletions src/types.tsx
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ export type DiagramDirection = "default" | "ltr" | "rtl";
export type DiagramLineStyle = "default" | "metro" | "straight" | "smooth";
export type DiagramLinks = "default" | "hidden" | "visible";
export type DiagramMode = "default" | "fields" | "summary" | "simple";
export type DiagramHoverFocus = "default" | "none" | "dim" | "recursive";
export type DriverType = "file" | "surrealkv" | "memory" | "tikv";
export type InvoiceStatus = "succeeded" | "pending" | "failed";
export type LogLevel = "error" | "warn" | "info" | "debug" | "trace";
@@ -123,6 +124,7 @@ export interface Connection {
diagramDirection: DiagramDirection;
diagramLineStyle: DiagramLineStyle;
diagramLinkMode: DiagramLinks;
diagramHoverFocus: DiagramHoverFocus;
diagramMode: DiagramMode;
designerTableList: boolean;
explorerTableList: boolean;
@@ -171,6 +173,7 @@ export interface SurrealistAppearanceSettings {
defaultDiagramLineStyle: DiagramLineStyle;
defaultDiagramLinkMode: DiagramLinks;
defaultDiagramMode: DiagramMode;
defaultDiagramHoverFocus: DiagramHoverFocus;
sidebarMode: SidebarMode;
queryOrientation: Orientation;
sidebarViews: Flags<ViewPage>;
2 changes: 2 additions & 0 deletions src/util/defaults.tsx
Original file line number Diff line number Diff line change
@@ -63,6 +63,7 @@ export function createBaseSettings(): SurrealistSettings {
defaultDiagramLineStyle: "metro",
defaultDiagramLinkMode: "visible",
defaultDiagramMode: "fields",
defaultDiagramHoverFocus: "none",
sidebarMode: "expandable",
queryOrientation: "vertical",
sidebarViews: {},
@@ -136,6 +137,7 @@ export function createBaseConnection(settings: SurrealistSettings): Connection {
diagramLineStyle: "default",
diagramMode: "default",
diagramLinkMode: "default",
diagramHoverFocus: "default",
graphqlQuery: "",
graphqlVariables: "{}",
graphqlShowVariables: false,
12 changes: 12 additions & 0 deletions src/util/preferences.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
DESIGNER_ALGORITHMS,
DESIGNER_DIRECTIONS,
DESIGNER_HOVER_FOCUS,
DESIGNER_LINE_STYLES,
DESIGNER_LINKS,
DESIGNER_NODE_MODES,
@@ -393,6 +394,17 @@ export function useComputedPreferences(): PreferenceSection[] {
},
}),
},
{
name: "Default relational hover focus",
description: "The default effect of relations when hovered",
controller: new SelectionController({
options: nodef(DESIGNER_HOVER_FOCUS),
reader: (config) => config.settings.appearance.defaultDiagramHoverFocus,
writer: (config, value) => {
config.settings.appearance.defaultDiagramHoverFocus = value;
},
}),
},
],
},
);