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

feat(visualizer): searchbar #1170

Merged
merged 13 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
21 changes: 17 additions & 4 deletions client/src/features/visualizer-threejs/VisualizerInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import CameraControls from "./CameraControls";
import "./Visualizer.scss";
import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer";
import { getBlockInitPosition, getBlockTargetPosition } from "./blockPositions";
import useSearchStore from "~features/visualizer-threejs/store/search";
import { useSearch } from "~features/visualizer-threejs/hooks/useSearch";

const features = {
statsEnabled: false,
Expand All @@ -54,6 +56,8 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
}),
);

const { isSearchMatch, highlightSearchBlock } = useSearch();

// Note: to prevent rerender each store update - call methods separate.
const isEdgeRenderingEnabled = useConfigStore((s) => s.isEdgeRenderingEnabled);
const setEdgeRenderingEnabled = useConfigStore((s) => s.setEdgeRenderingEnabled);
Expand All @@ -66,6 +70,7 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
const blockMetadata = useTangleStore((s) => s.blockMetadata);
const indexToBlockId = useTangleStore((s) => s.indexToBlockId);
const clickedInstanceId = useTangleStore((s) => s.clickedInstanceId);
const matchingBlockIds = useSearchStore((state) => state.matchingBlockIds);

// Confirmed or accepted blocks by slot
const confirmedBlocksBySlot = useTangleStore((s) => s.confirmedBlocksBySlot);
Expand Down Expand Up @@ -208,7 +213,7 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
bpsCounter.start();
}

blockMetadata.set(blockData.blockId, blockData);
blockMetadata.set(blockData.blockId, { ...blockData, treeColor: PENDING_BLOCK_COLOR });

// edges
const blockStrongParents = (blockData.block.body as BasicBlockBody).strongParents ?? [];
Expand All @@ -220,6 +225,10 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
addToEdgeQueue(blockData.blockId, blockWeakParents);
}

if (isSearchMatch(blockData)) {
highlightSearchBlock(blockData.blockId);
}

addBlock({
id: blockData.blockId,
color: PENDING_BLOCK_COLOR,
Expand All @@ -234,7 +243,13 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
if (metadataUpdate?.blockState) {
const selectedColor = BLOCK_STATE_TO_COLOR.get(metadataUpdate.blockState);
if (selectedColor) {
addToColorQueue(metadataUpdate.blockId, selectedColor);
const currentBlockMetadata = blockMetadata.get(metadataUpdate.blockId);
if (currentBlockMetadata) {
currentBlockMetadata.treeColor = selectedColor;
}
if (!matchingBlockIds.includes(metadataUpdate.blockId)) {
addToColorQueue(metadataUpdate.blockId, selectedColor);
}
}

const acceptedStates: BlockState[] = ["confirmed", "accepted"];
Expand Down Expand Up @@ -265,11 +280,9 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
<Wrapper
key={network}
blocksCount={indexToBlockId.length}
filter=""
isPlaying={isPlaying}
network={network}
networkConfig={networkConfig}
onChangeFilter={() => {}}
selectNode={() => {}}
selectedFeedItem={selectedFeedItem}
setIsPlaying={setIsPlaying}
Expand Down
14 changes: 10 additions & 4 deletions client/src/features/visualizer-threejs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ export const DATA_SENDER_TIME_INTERVAL = 500;
export const ANIMATION_TIME_SECONDS = 3;

// colors
export const PENDING_BLOCK_COLOR = new Color("#A6C3FC");
export const ACCEPTED_BLOCK_COLOR = new Color("#0101AB");
export const CONFIRMED_BLOCK_COLOR = new Color("#0000DB");
export const FINALIZED_BLOCK_COLOR = new Color("#0101FF");
export const PENDING_BLOCK_COLOR_HASH = "#A6C3FC";
export const PENDING_BLOCK_COLOR = new Color(PENDING_BLOCK_COLOR_HASH);
export const ACCEPTED_BLOCK_COLOR_HASH = "#0101AB";
export const ACCEPTED_BLOCK_COLOR = new Color(ACCEPTED_BLOCK_COLOR_HASH);
export const CONFIRMED_BLOCK_COLOR_HASH = "#0000DB";
export const CONFIRMED_BLOCK_COLOR = new Color(CONFIRMED_BLOCK_COLOR_HASH);
export const FINALIZED_BLOCK_COLOR_HASH = "#0101FF";
export const FINALIZED_BLOCK_COLOR = new Color(FINALIZED_BLOCK_COLOR_HASH);
export const SEARCH_RESULT_COLOR_HASH = "#C026D3";
export const SEARCH_RESULT_COLOR = new Color(SEARCH_RESULT_COLOR_HASH);
VmMad marked this conversation as resolved.
Show resolved Hide resolved
// TODO Remove accepted state once is added to the SDK (missing)
export const BLOCK_STATE_TO_COLOR = new Map<BlockState | "accepted", Color>([
["pending", PENDING_BLOCK_COLOR],
Expand Down
4 changes: 2 additions & 2 deletions client/src/features/visualizer-threejs/hooks/useMouseMove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useThree } from "@react-three/fiber";
import React, { useCallback, useState, useRef, useEffect } from "react";
import * as THREE from "three";
import { useTangleStore } from "../store";
import { SEARCH_RESULT_COLOR } from "~features/visualizer-threejs/constants";

export const useMouseMove = ({
tangleMeshRef,
Expand Down Expand Up @@ -40,7 +41,6 @@ export const useMouseMove = ({
cb(null);
} else {
const { instanceId } = firstIntersect;
const color = new THREE.Color(0xff0000); // Red color

// If we're hovering over a new instance, save the current color and set to red
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
if (hoveredInstanceId !== instanceId) {
Expand All @@ -51,7 +51,7 @@ export const useMouseMove = ({
originalColorsRef.current.set(instanceId, currentColor);

// Set the new hovered instance color to red
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
tangleMeshRef.current.setColorAt(instanceId, color);
tangleMeshRef.current.setColorAt(instanceId, SEARCH_RESULT_COLOR);
VmMad marked this conversation as resolved.
Show resolved Hide resolved
cb(instanceId);
}
}
Expand Down
44 changes: 44 additions & 0 deletions client/src/features/visualizer-threejs/hooks/useSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// import React from "react";
import { IFeedBlockData } from "~models/api/nova/feed/IFeedBlockData";
import useSearchStore from "~features/visualizer-threejs/store/search";
import { useTangleStore } from "~features/visualizer-threejs/store";
import { SEARCH_RESULT_COLOR } from "~features/visualizer-threejs/constants";

export const useSearch = () => {
const searchQuery = useSearchStore((state) => state.searchQuery);
const addToColorQueue = useTangleStore((s) => s.addToColorQueue);

const isSearchMatch = (block: IFeedBlockData) => {
const searchQuery = useSearchStore.getState().searchQuery;
return matchLatestFinalizedSlot(block, searchQuery) || matchByBlockId(block, searchQuery);
};

const highlightSearchBlock = (blockId: string) => {
useSearchStore.getState().addMatchingBlockId(blockId);
addToColorQueue(blockId, SEARCH_RESULT_COLOR);
};

return {
searchQuery,
isSearchMatch,
highlightSearchBlock,
};
};

export function isSearchMatch(block: IFeedBlockData, searchQuery: string) {
return matchLatestFinalizedSlot(block, searchQuery) || matchByBlockId(block, searchQuery); // note: here we can add more checks as separate functions
VmMad marked this conversation as resolved.
Show resolved Hide resolved
}

export function matchByBlockId(block: IFeedBlockData, searchQuery: string) {
return block.blockId === searchQuery;
}

export function matchLatestFinalizedSlot(block: IFeedBlockData, searchQuery: string) {
if (!searchQuery || Number.isNaN(Number(searchQuery))) {
return false;
}

const latestFinalizedSlot = block?.block?.header?.latestFinalizedSlot;

return latestFinalizedSlot === Number(searchQuery);
}
26 changes: 26 additions & 0 deletions client/src/features/visualizer-threejs/store/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { create } from "zustand";

interface SearchState {
searchQuery: string;
matchingBlockIds: string[];
setSearchQuery: (query: string) => void;
setMatchingBlockIds: (ids: string[]) => void;
addMatchingBlockId: (id: string) => void;
}

// Create the store
const useSearchStore = create<SearchState>((set) => ({
searchQuery: "",
matchingBlockIds: [],

// Action to update the search query
setSearchQuery: (query) => set({ searchQuery: query }),

// Action to update the block IDs
setMatchingBlockIds: (ids) => set({ matchingBlockIds: ids }),

// Action to add a block ID
addMatchingBlockId: (id) => set((state) => ({ matchingBlockIds: [...state.matchingBlockIds, id] })),
}));

export default useSearchStore;
9 changes: 8 additions & 1 deletion client/src/features/visualizer-threejs/store/tangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ interface TangleState {

colorQueue: Pick<IBlockState, "id" | "color">[];
addToColorQueue: (blockId: string, color: Color) => void;
addToColorQueueBulk: (list: { id: string; color: Color }[]) => void;
removeFromColorQueue: (blockIds: string[]) => void;

// Map of blockId to index in Tangle 'InstancedMesh'
blockIdToIndex: Map<string, number>;
blockIdToEdges: Map<string, EdgeEntry>;
blockIdToPosition: Map<string, [x: number, y: number, z: number]>;
blockMetadata: Map<string, IFeedBlockData>;
blockMetadata: Map<string, IFeedBlockData & { treeColor: Color }>;

indexToBlockId: string[];
updateBlockIdToIndex: (blockId: string, index: number) => void;
Expand Down Expand Up @@ -169,6 +170,12 @@ export const useTangleStore = create<TangleState>()(
colorQueue: [...state.colorQueue, { id, color }],
}));
},
addToColorQueueBulk: (list) => {
set((state) => ({
...state,
colorQueue: [...state.colorQueue, ...list],
}));
},
removeFromColorQueue: (blockIds) => {
if (blockIds.length > 0) {
set((state) => ({
Expand Down
23 changes: 17 additions & 6 deletions client/src/features/visualizer-threejs/wrapper/KeyPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
import React, { memo } from "react";
import { BlockState } from "@iota/sdk-wasm-nova/web";

import {
ACCEPTED_BLOCK_COLOR_HASH,
CONFIRMED_BLOCK_COLOR_HASH,
FINALIZED_BLOCK_COLOR_HASH,
PENDING_BLOCK_COLOR_HASH,
SEARCH_RESULT_COLOR_HASH,
} from "../constants";
import "./KeyPanel.scss";
import StatsPanel from "~features/visualizer-threejs/wrapper/StatsPanel";

export const KeyPanel = ({ network }: { network: string }) => {
const statuses: {
label: string;
state: BlockState;
state: BlockState | "searchResult";
color: string;
}[] = [
{
label: "Pending",
state: "pending",
color: "#A6C3FC",
color: PENDING_BLOCK_COLOR_HASH,
},
VmMad marked this conversation as resolved.
Show resolved Hide resolved
{
label: "Accepted",
state: "accepted",
color: "#0101AB",
color: ACCEPTED_BLOCK_COLOR_HASH,
},
{
label: "Confirmed",
state: "confirmed",
color: "#0000DB",
color: CONFIRMED_BLOCK_COLOR_HASH,
},
{
label: "Finalized",
state: "finalized",
color: "#0101FF",
color: FINALIZED_BLOCK_COLOR_HASH,
},
{
label: "Rejected",
Expand All @@ -40,6 +46,11 @@ export const KeyPanel = ({ network }: { network: string }) => {
state: "failed",
color: "#ff1d38",
},
{
label: "Search result",
state: "searchResult",
color: SEARCH_RESULT_COLOR_HASH,
},
];

return (
Expand Down
Loading