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): update block colors #1193

Merged
merged 6 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion client/src/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import StardustSearch from "./routes/stardust/Search";
import StardustStatisticsPage from "./routes/stardust/statistics/StatisticsPage";
import StardustTransactionPage from "./routes/stardust/TransactionPage";
import { Visualizer as StardustVisualizer } from "./routes/stardust/Visualizer";
import NovaVisualizer from "../features/visualizer-threejs/VisualizerInstance";
import NovaVisualizer from "../features/visualizer-threejs/NovaVisualizer";
import StreamsV0 from "./routes/StreamsV0";
import { StreamsV0RouteProps } from "./routes/StreamsV0RouteProps";
import { VisualizerRouteProps } from "./routes/VisualizerRouteProps";
Expand Down
11 changes: 11 additions & 0 deletions client/src/features/visualizer-threejs/NovaVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import { useGetThemeMode } from "~/helpers/hooks/useGetThemeMode.js";
import { VisualizerRouteProps } from "~/app/routes/VisualizerRouteProps.js";
import { RouteComponentProps } from "react-router-dom";
import VisualizerInstance from "./VisualizerInstance";

export default function NovaVisualizer(props: RouteComponentProps<VisualizerRouteProps>): React.JSX.Element {
const theme = useGetThemeMode();

return <VisualizerInstance key={theme} {...props} />;
}
49 changes: 28 additions & 21 deletions client/src/features/visualizer-threejs/VisualizerInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@ import { Perf } from "r3f-perf";
import React, { useEffect, useRef } from "react";
import { RouteComponentProps } from "react-router-dom";
import * as THREE from "three";
import {
FAR_PLANE,
features,
NEAR_PLANE,
DIRECTIONAL_LIGHT_INTENSITY,
PENDING_BLOCK_COLOR,
VISUALIZER_BACKGROUND,
BLOCK_STATE_TO_COLOR,
} from "./constants";
import { FAR_PLANE, features, NEAR_PLANE, DIRECTIONAL_LIGHT_INTENSITY, VISUALIZER_BACKGROUND } from "./constants";
import Emitter from "./Emitter";
import { useTangleStore, useConfigStore } from "./store";
import { BPSCounter } from "./BPSCounter";
Expand All @@ -30,7 +22,7 @@ import { IFeedBlockData } from "~/models/api/nova/feed/IFeedBlockData";
import CameraControls from "./CameraControls";
import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer";
import { getBlockInitPosition, getBlockTargetPosition } from "./blockPositions";
import { getCurrentTiltValue } from "./utils";
import { getBlockColorByState, getCurrentTiltValue } from "./utils";
import useSearchStore from "~features/visualizer-threejs/store/search";
import { useSearch } from "~features/visualizer-threejs/hooks/useSearch";
import "./Visualizer.scss";
Expand Down Expand Up @@ -69,6 +61,9 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
const clickedInstanceId = useTangleStore((s) => s.clickedInstanceId);
const matchingBlockIds = useSearchStore((state) => state.matchingBlockIds);

const blockIdToState = useTangleStore((s) => s.blockIdToState);
const setBlockIdToBlockState = useTangleStore((s) => s.setBlockIdToBlockState);

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

blockMetadata.set(blockData.blockId, { ...blockData, treeColor: PENDING_BLOCK_COLOR });
const blockColor = getBlockColorByState(themeMode, "pending");
blockMetadata.set(blockData.blockId, { ...blockData, treeColor: blockColor });

// edges
const blockStrongParents = (blockData.block.body as BasicBlockBody).strongParents ?? [];
Expand All @@ -235,9 +231,11 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
highlightSearchBlock(blockData.blockId);
}

setBlockIdToBlockState(blockData.blockId, "pending");

addBlock({
id: blockData.blockId,
color: PENDING_BLOCK_COLOR,
color: blockColor,
blockAddedTimestamp: currentAnimationTime,
targetPosition,
initPosition,
Expand All @@ -247,22 +245,29 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =

function onBlockMetadataUpdate(metadataUpdate: IBlockMetadata): void {
if (metadataUpdate?.blockState) {
const selectedColor = BLOCK_STATE_TO_COLOR.get(metadataUpdate.blockState);
const selectedColor = getBlockColorByState(themeMode, metadataUpdate.blockState);
if (selectedColor) {
const currentBlockMetadata = blockMetadata.get(metadataUpdate.blockId);
if (currentBlockMetadata) {
currentBlockMetadata.treeColor = selectedColor;
}
if (!matchingBlockIds.includes(metadataUpdate.blockId)) {
addToColorQueue(metadataUpdate.blockId, selectedColor);

const previousBlockState = blockIdToState.get(metadataUpdate.blockId);
const wasConfirmedBeforeAccepted = previousBlockState === "accepted" && metadataUpdate.blockState === "confirmed";

if (!wasConfirmedBeforeAccepted) {
setBlockIdToBlockState(metadataUpdate.blockId, metadataUpdate.blockState);
if (!matchingBlockIds.includes(metadataUpdate.blockId)) {
addToColorQueue(metadataUpdate.blockId, selectedColor);
}
}
}

const acceptedStates: BlockState[] = ["confirmed", "accepted"];
const acceptedStates: BlockState[] = ["confirmed", "accepted"];

if (acceptedStates.includes(metadataUpdate.blockState)) {
const slot = Utils.computeSlotIndex(metadataUpdate.blockId);
addToConfirmedBlocksSlot(metadataUpdate.blockId, slot);
if (acceptedStates.includes(metadataUpdate.blockState)) {
const slot = Utils.computeSlotIndex(metadataUpdate.blockId);
addToConfirmedBlocksSlot(metadataUpdate.blockId, slot);
}
}
}
}
Expand All @@ -272,9 +277,10 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =

if (blocks?.length) {
blocks.forEach((blockId) => {
const selectedColor = BLOCK_STATE_TO_COLOR.get("finalized");
const selectedColor = getBlockColorByState(themeMode, "finalized");
if (selectedColor) {
addToColorQueue(blockId, selectedColor);
setBlockIdToBlockState(blockId, "finalized");
}
});
}
Expand All @@ -294,6 +300,7 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
setIsPlaying={setIsPlaying}
isEdgeRenderingEnabled={isEdgeRenderingEnabled}
setEdgeRenderingEnabled={(checked) => setEdgeRenderingEnabled(checked)}
themeMode={themeMode}
>
<Canvas
ref={canvasRef}
Expand Down
42 changes: 30 additions & 12 deletions client/src/features/visualizer-threejs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,37 @@ export const SECOND = 1000;
export const DATA_SENDER_TIME_INTERVAL = 500;

// 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 SEARCH_RESULT_COLOR = new Color("#C026D3");
export const ACCEPTED_BLOCK_COLORS: Color[] = [new Color("#0101FF"), new Color("#0000DB"), new Color("#0101AB")];
export const CONFIRMED_BLOCK_COLOR = new Color("#3CE5E1");
export const FAILED_BLOCK_COLOR = new Color("#C026D3");
export const REJECTED_BLOCK_COLOR = FAILED_BLOCK_COLOR;
export const SEARCH_RESULT_COLOR = new Color("#1EC15A");
export const HOVERED_BLOCK_COLOR = SEARCH_RESULT_COLOR;

export const BLOCK_STATE_TO_COLOR = new Map<BlockState, Color>([
["pending", PENDING_BLOCK_COLOR],
["accepted", ACCEPTED_BLOCK_COLOR],
["confirmed", CONFIRMED_BLOCK_COLOR],
["finalized", FINALIZED_BLOCK_COLOR],
]);
// colors by theme
export const PENDING_BLOCK_COLOR_LIGHTMODE = new Color("#A6C3FC");
export const PENDING_BLOCK_COLOR_DARKMODE = new Color("#5C84FA");
export const FINALIZED_BLOCK_COLOR_LIGHTMODE = new Color("#5C84FA");
export const FINALIZED_BLOCK_COLOR_DARKMODE = new Color("#000081");

export const THEME_BLOCK_COLORS: Record<ThemeMode, Record<BlockState, Color | Color[]>> = {
[ThemeMode.Dark]: {
accepted: ACCEPTED_BLOCK_COLORS,
pending: PENDING_BLOCK_COLOR_DARKMODE,
confirmed: CONFIRMED_BLOCK_COLOR,
finalized: FINALIZED_BLOCK_COLOR_DARKMODE,
rejected: REJECTED_BLOCK_COLOR,
failed: FAILED_BLOCK_COLOR,
},
[ThemeMode.Light]: {
accepted: ACCEPTED_BLOCK_COLORS,
pending: PENDING_BLOCK_COLOR_LIGHTMODE,
confirmed: CONFIRMED_BLOCK_COLOR,
finalized: FINALIZED_BLOCK_COLOR_LIGHTMODE,
rejected: REJECTED_BLOCK_COLOR,
failed: FAILED_BLOCK_COLOR,
},
};

// emitter
export const EMITTER_SPEED_MULTIPLIER = 150;
Expand Down Expand Up @@ -64,7 +82,7 @@ export const DIRECTIONAL_LIGHT_INTENSITY = 0.45;

export const VISUALIZER_BACKGROUND: Record<ThemeMode, string> = {
[ThemeMode.Dark]: "#000000",
[ThemeMode.Light]: "#f2f2f2",
[ThemeMode.Light]: "#FFFFFF",
};

// emitter
Expand Down
49 changes: 42 additions & 7 deletions client/src/features/visualizer-threejs/store/tangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { devtools } from "zustand/middleware";
import { ZOOM_DEFAULT, SPRAY_DISTANCE } from "../constants";
import { IFeedBlockData } from "~models/api/nova/feed/IFeedBlockData";
import { IThreeDimensionalPosition } from "../interfaces";
import { BlockId, SlotIndex } from "@iota/sdk-wasm-nova/web";
import { BlockId, BlockState, SlotIndex } from "@iota/sdk-wasm-nova/web";
import { getVisualizerConfigValues } from "~features/visualizer-threejs/ConfigControls";

export interface IBlockAnimationPosition {
Expand Down Expand Up @@ -80,6 +80,9 @@ interface TangleState {
confirmedBlocksBySlot: Map<number, string[]>;
addToConfirmedBlocksBySlot: (blockId: BlockId, slot: SlotIndex) => void;
removeConfirmedBlocksSlot: (slot: SlotIndex) => void;

blockIdToState: Map<BlockId, BlockState>;
setBlockIdToBlockState: (blockId: BlockId, blockState: BlockState) => void;
Comment on lines +84 to +85
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that we have a lot of data that related to blockId.
And usually when we need to remove block, we need to go through all maps, like:
blockIdToIndex, blockIdToEdges, blockIdToPosition, blockMetadata, blockIdToAnimationPosition, blockIdToState.

Maybe it's a good sign to:

  1. Store all of this mappers one under another.
blockIdToIndex: Map<string, number>;
blockIdToEdges: Map<string, EdgeEntry>;
blockIdToPosition: Map<string, [x: number, y: number, z: number]>;
blockIdToMetadata: Map<string, IFeedBlockData & { treeColor: Color }>; // rename it from blockMetadata
blockIdToAnimationPosition: Map<string, IBlockAnimationPosition>;
blockIdToState: Map<BlockId, BlockState>;
  1. Create function for clear all dependent mapper's data at once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a new issue for this:
#1196

}

const INITIAL_STATE = {
Expand All @@ -97,12 +100,32 @@ const INITIAL_STATE = {
bps: 0,
clickedInstanceId: null,
confirmedBlocksBySlot: new Map(),
blockIdToState: new Map(),
};

export const useTangleStore = create<TangleState>()(
devtools((set) => ({
...INITIAL_STATE,
resetConfigState: () => set(INITIAL_STATE),
resetConfigState: () =>
// hard cleanup of the store
set((state) => {
state.blockQueue = [];
state.edgeQueue = [];
state.colorQueue = [];
state.blockIdToEdges = new Map();
state.blockIdToIndex = new Map();
state.blockIdToPosition = new Map();
state.blockMetadata = new Map();
state.blockIdToAnimationPosition = new Map();
state.indexToBlockId = [];
state.zoom = ZOOM_DEFAULT;
state.forcedZoom = undefined;
state.bps = 0;
state.clickedInstanceId = null;
state.confirmedBlocksBySlot = new Map();
state.blockIdToState = new Map();
return state;
}),
updateBlockIdToAnimationPosition: (updatedPositions) => {
set((state) => {
updatedPositions.forEach((value, key) => {
Expand Down Expand Up @@ -196,15 +219,18 @@ export const useTangleStore = create<TangleState>()(
updateBlockIdToIndex: (blockId: string, index: number) => {
set((state) => {
state.blockIdToIndex.set(blockId, index);
if (state.indexToBlockId[index]) {
const previousBlockId = state.indexToBlockId[index];
if (previousBlockId) {
Comment on lines +222 to +223
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see. Maybe this is something that I was talking about in comment above.

// Clean up map from old blockIds
state.blockIdToIndex.delete(state.indexToBlockId[index]);
state.blockIdToIndex.delete(previousBlockId);
// Clean up old block edges
state.blockIdToEdges.delete(state.indexToBlockId[index]);
state.blockIdToEdges.delete(previousBlockId);
// Clean up old block position
state.blockIdToPosition.delete(state.indexToBlockId[index]);
state.blockIdToPosition.delete(previousBlockId);
// Clean up old block metadata
state.blockMetadata.delete(state.indexToBlockId[index]);
state.blockMetadata.delete(previousBlockId);
// Cleanup old block state
state.blockIdToState.delete(previousBlockId);
}

const nextIndexToBlockId = [...state.indexToBlockId];
Expand Down Expand Up @@ -268,5 +294,14 @@ export const useTangleStore = create<TangleState>()(
};
});
},
setBlockIdToBlockState(blockId, blockState) {
set((state) => {
state.blockIdToState.set(blockId, blockState);
return {
...state,
blockIdToState: state.blockIdToState,
};
});
},
})),
);
12 changes: 12 additions & 0 deletions client/src/features/visualizer-threejs/useRenderTangle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const useRenderTangle = () => {
const blockIdToAnimationPosition = useTangleStore((s) => s.blockIdToAnimationPosition);
const updateBlockIdToAnimationPosition = useTangleStore((s) => s.updateBlockIdToAnimationPosition);

const resetTangleStore = useTangleStore((s) => s.resetConfigState);

const getVisualizerTimeDiff = useVisualizerTimer();

const assignBlockToMesh = (block: IBlockState) => {
Expand Down Expand Up @@ -197,4 +199,14 @@ export const useRenderTangle = () => {
setUpdateAnimationPositionQueue(updateAnimationPositionQueue);
}
}, [isPlaying, updateAnimationPositionQueue]);

/**
* Cleanup scene
*/
useEffect(() => {
return () => {
scene.remove(tangleMeshRef.current);
resetTangleStore();
};
}, [scene, tangleMeshRef]);
};
14 changes: 14 additions & 0 deletions client/src/features/visualizer-threejs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ import {
MAX_PREV_POINTS,
MAX_POINT_RETRIES,
MIN_BLOCK_NEAR_RADIUS,
THEME_BLOCK_COLORS,
} from "./constants";
import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition, ITwoDimensionalPosition } from "./interfaces";
import { getVisualizerConfigValues } from "~features/visualizer-threejs/ConfigControls";
import { BlockState } from "@iota/sdk-wasm-nova/web";
import { ThemeMode } from "./enums";

/**
* Generates a random number within a specified range.
Expand Down Expand Up @@ -384,3 +387,14 @@ export function getCurrentTiltValue(animationTime: number, tilts: number[]): num

return currentTilt;
}

export function getBlockColorByState(theme: ThemeMode, blockState: BlockState): THREE.Color {
const targetColor = THEME_BLOCK_COLORS[theme][blockState];

if (Array.isArray(targetColor)) {
const index = randomIntFromInterval(0, targetColor.length - 1);
return targetColor[index];
}

return targetColor;
}
29 changes: 29 additions & 0 deletions client/src/features/visualizer-threejs/wrapper/ColorPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { memo } from "react";

const ColorPanel = ({ label, color }: { label: string; color: string | string[] }): React.JSX.Element => (
<div className="key-panel-item">
{Array.isArray(color) ? (
<div className="key-panel-item-multi-color">
{color.map((c, index) => (
<div
key={`${label}-${index}`}
className="key-marker"
style={{
backgroundColor: c,
}}
/>
))}
</div>
) : (
<div
className="key-marker"
style={{
backgroundColor: color,
}}
/>
)}
<div className="key-label">{label}</div>
</div>
);

export default memo(ColorPanel);
8 changes: 8 additions & 0 deletions client/src/features/visualizer-threejs/wrapper/KeyPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
border-radius: 50%;
}

.key-panel-item-multi-color {
display: flex;
flex-direction: row;
.key-marker:not(:last-of-type) {
margin-right: 4px;
}
}

.key-label {
@include font-size(14px);

Expand Down
Loading
Loading