Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • graphpolaris/frontend-v2
  • rijkheere/frontend-v-2-reordering-paoh
2 results
Show changes
Showing
with 230 additions and 178 deletions
......@@ -4,6 +4,7 @@
* © Copyright Utrecht University (Department of Information and Computing Sciences)
*/
import { getEnvVariable } from 'config';
import { UseIsAuthorizedState } from '../store/authSlice';
import { ReceiveMessageI, SendMessageI, SendMessageWithSessionI } from './types';
......@@ -38,7 +39,7 @@ export class Broker {
/** Get the singleton instance of the Broker. */
public static instance(): Broker {
if (!this.singletonInstance) this.singletonInstance = new Broker(import.meta.env.BACKEND_WSS_URL);
if (!this.singletonInstance) this.singletonInstance = new Broker(String(getEnvVariable('BACKEND_WSS_URL')));
return this.singletonInstance;
}
......
import { getEnvVariable } from 'config';
import { useAppDispatch, useAuthCache } from '../store';
import { authenticated, changeRoom, UserAuthenticationHeader } from '../store/authSlice';
import { addInfo, addError } from '../store/configSlice';
const domain = import.meta.env.BACKEND_URL;
const userURI = import.meta.env.BACKEND_USER;
const domain = getEnvVariable('BACKEND_URL');
const userURI = getEnvVariable('BACKEND_USER');
export const fetchSettings: RequestInit = {
method: 'GET',
......@@ -16,15 +18,15 @@ export const useAuthentication = () => {
const handleError = (err: any) => {
console.error(err);
dispatch(addError('Failed to copy: ' + (err.message || 'Unknown error occurred')));
};
const login = () => {
fetch(`${domain}${userURI}/headers`, fetchSettings)
.then((res) => {
.then((res) =>
res
.json()
.then((res: UserAuthenticationHeader) => {
console.log(res, 'headers');
dispatch(
authenticated({
username: res.username,
......@@ -36,24 +38,47 @@ export const useAuthentication = () => {
}),
);
})
.catch(handleError);
})
.catch(handleError);
};
const newShareRoom = () => {
fetch(`${domain}${userURI}/share`, { ...fetchSettings, method: 'POST' })
.then((res) =>
res
.json()
.then((res: { Roomid: string; Sessionid: string }) => {
// TODO: send to backend current state and make redux accordingly
dispatch(changeRoom(res.Roomid));
})
.catch(handleError),
)
.catch(handleError);
};
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.error('Failed to copy:', err);
return false;
}
};
const newShareRoom = async () => {
try {
// TODO: Implement share room functionality when backend is ready
// fetch(`${domain}${userURI}/share`, { ...fetchSettings, method: 'POST' })
// .then((res) =>
// res
// .json()
// .then((res: { Roomid: string; Sessionid: string }) => {
// dispatch(changeRoom(res.Roomid));
// })
// .catch(handleError),
// )
// .catch(handleError);
const shareUrl = window.location.href;
const copied = await copyToClipboard(shareUrl);
if (copied) {
dispatch(addInfo('Link copied to clipboard!'));
} else {
throw new Error('Failed to copy to clipboard');
}
} catch (error: any) {
handleError(error);
}
};
return { login, newShareRoom };
};
};
\ No newline at end of file
......@@ -20,7 +20,6 @@ import {
queryBuilderState,
selectQuerybuilderGraph,
selectQuerybuilderHash,
queryUnionTypes,
QueryUnionType
} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { activeSaveState, activeSaveStateAuthorization, SessionCacheI, sessionCacheState } from './sessionSlice';
......@@ -69,7 +68,6 @@ export const useQuerybuilderHash: () => string = () => useAppSelector(selectQuer
export const useQuerybuilderSettings: () => QueryBuilderSettings = () => useAppSelector(queryBuilderSettingsState);
export const useQuerybuilder: () => QueryBuilderState = () => useAppSelector(queryBuilderState);
export const useQuerybuilderAttributesShown: () => QueryGraphEdgeHandle[] = () => useAppSelector(queryBuilderAttributesShown);
export const useQuerybuilderUnionTypes: () => {[nodeId: string]: QueryUnionType} = () => useAppSelector(queryUnionTypes);
// Overall Configuration of the app
export const useConfig: () => ConfigStateI = () => useAppSelector(configState);
......
......@@ -13,6 +13,7 @@ export type QueryBuilderSettings = {
depth: { min: number; max: number };
layout: AllLayoutAlgorithms | 'manual';
autocompleteRelation: boolean;
unionTypes: { [nodeId: string]: QueryUnionType };
};
export type QueryBuilderText = {
......@@ -33,7 +34,6 @@ export type QueryBuilderState = {
settings: QueryBuilderSettings;
queryTranslation: QueryBuilderText;
attributesBeingShown: QueryGraphEdgeHandle[];
unionTypes: { [nodeId: string]: QueryUnionType };
};
export type SchemaState = {
......@@ -49,13 +49,13 @@ export const initialState: QueryBuilderState = {
depth: { min: 1, max: 1 },
layout: 'manual',
autocompleteRelation: true,
unionTypes: {},
},
queryTranslation: {
queryId: '',
result: '',
},
attributesBeingShown: [],
unionTypes: {},
// schemaLayout: 'Graphology_noverlap',
} as QueryBuilderState;
......@@ -100,7 +100,10 @@ export const querybuilderSlice = createSlice({
}
},
setQueryUnionType: (state: QueryBuilderState, action: PayloadAction<{ nodeId: string; unionType: QueryUnionType }>) => {
state.unionTypes[action.payload.nodeId] = action.payload.unionType;
if (state.settings.unionTypes == null) {
state.settings.unionTypes = {};
}
state.settings.unionTypes[action.payload.nodeId] = action.payload.unionType;
},
},
});
......@@ -169,6 +172,4 @@ export const {
setQueryUnionType,
} = querybuilderSlice.actions;
export const queryBuilderAttributesShown = (state: RootState) => state.querybuilder.attributesBeingShown;
export const queryUnionTypes = (state: RootState) => state.querybuilder.unionTypes;
export const queryBuilderAttributesShown = (state: RootState) => state.querybuilder.attributesBeingShown;
\ No newline at end of file
......@@ -5,8 +5,8 @@ import { isEqual } from 'lodash-es';
export type VisStateSettings = VisualizationSettingsType[];
export type VisState = {
activeVisualizationIndex: number; // uses underscore_case to match data model from backend
openVisualizationArray: VisStateSettings; // uses underscore_case to match data model from backend
activeVisualizationIndex: number;
openVisualizationArray: VisStateSettings;
};
export const initialState: VisState = {
......@@ -19,48 +19,58 @@ export const visualizationSlice = createSlice({
initialState,
reducers: {
removeVisualization: (state, action: PayloadAction<number | undefined>) => {
let index = action.payload || state.activeVisualizationIndex;
state.openVisualizationArray.splice(index, 1);
if (state.activeVisualizationIndex === index) {
if (state.openVisualizationArray.length === 0) state.activeVisualizationIndex = -1;
else {
state.activeVisualizationIndex = Math.max(state.openVisualizationArray.length - 1, 0);
const index = action.payload ?? state.activeVisualizationIndex;
if (index >= 0 && index < state.openVisualizationArray.length) {
state.openVisualizationArray.splice(index, 1);
if (state.openVisualizationArray.length === 0) {
state.activeVisualizationIndex = -1;
} else if (state.activeVisualizationIndex >= state.openVisualizationArray.length) {
state.activeVisualizationIndex = state.openVisualizationArray.length - 1;
} else if (state.activeVisualizationIndex === index) {
state.activeVisualizationIndex = index < state.openVisualizationArray.length ? index : index - 1;
} else if (state.activeVisualizationIndex > index) {
state.activeVisualizationIndex--;
}
}
},
setActiveVisualization: (state, action: PayloadAction<number>) => {
state.activeVisualizationIndex = action.payload;
if (action.payload >= -1 && action.payload < state.openVisualizationArray.length) {
state.activeVisualizationIndex = action.payload;
}
},
setVisualizationState: (state, action: PayloadAction<VisState>) => {
if (action.payload.activeVisualizationIndex !== undefined && !isEqual(action.payload, state)) {
state.openVisualizationArray = action.payload.openVisualizationArray || [];
state.activeVisualizationIndex = action.payload.activeVisualizationIndex;
state.activeVisualizationIndex = Math.min(
action.payload.activeVisualizationIndex,
state.openVisualizationArray.length - 1
);
}
},
updateVisualization: (state, action: PayloadAction<{ id: number; settings: VisualizationSettingsType }>) => {
const { id, settings } = action.payload;
state.openVisualizationArray[id] = settings;
if (id >= 0 && id < state.openVisualizationArray.length) {
state.openVisualizationArray[id] = settings;
}
},
addVisualization: (state, action: PayloadAction<VisualizationSettingsType>) => {
state.openVisualizationArray.push(action.payload);
state.activeVisualizationIndex = state.openVisualizationArray.length - 1;
},
updateActiveVisualization: (state, action: PayloadAction<VisualizationSettingsType>) => {
if (state.activeVisualizationIndex === -1) {
return;
if (state.activeVisualizationIndex >= 0) {
state.openVisualizationArray[state.activeVisualizationIndex] = action.payload;
}
state.openVisualizationArray[state.activeVisualizationIndex] = action.payload;
},
updateActiveVisualizationAttributes: (state, action: PayloadAction<Record<string, any>>) => {
if (state.activeVisualizationIndex === -1) {
return;
if (state.activeVisualizationIndex >= 0) {
state.openVisualizationArray[state.activeVisualizationIndex] = {
...state.openVisualizationArray[state.activeVisualizationIndex],
...action.payload,
};
}
state.openVisualizationArray[state.activeVisualizationIndex] = {
...state.openVisualizationArray[state.activeVisualizationIndex],
...action.payload,
};
},
reorderVisState: (
state,
......@@ -70,14 +80,30 @@ export const visualizationSlice = createSlice({
}>,
) => {
const { id, newPosition } = action.payload;
const settingsCopy = [...state.openVisualizationArray];
settingsCopy.splice(newPosition, 0, settingsCopy.splice(id, 1)[0]);
state.openVisualizationArray = settingsCopy;
if (
id >= 0 &&
id < state.openVisualizationArray.length &&
newPosition >= 0 &&
newPosition < state.openVisualizationArray.length
) {
const settingsCopy = [...state.openVisualizationArray];
const [movedItem] = settingsCopy.splice(id, 1);
settingsCopy.splice(newPosition, 0, movedItem);
state.openVisualizationArray = settingsCopy;
if (state.activeVisualizationIndex === id) {
state.activeVisualizationIndex = newPosition;
} else if (state.activeVisualizationIndex === newPosition) {
state.activeVisualizationIndex = id;
if (state.activeVisualizationIndex === id) {
state.activeVisualizationIndex = newPosition;
} else if (
state.activeVisualizationIndex > id &&
state.activeVisualizationIndex <= newPosition
) {
state.activeVisualizationIndex--;
} else if (
state.activeVisualizationIndex < id &&
state.activeVisualizationIndex >= newPosition
) {
state.activeVisualizationIndex++;
}
}
},
},
......@@ -100,4 +126,4 @@ export const visualizationActive = (state: RootState) =>
state.visualize.activeVisualizationIndex !== -1
? state.visualize.openVisualizationArray?.[state.visualize.activeVisualizationIndex]
: undefined;
export default visualizationSlice.reducer;
export default visualizationSlice.reducer;
\ No newline at end of file
......@@ -9,9 +9,10 @@ import { SelectionConfig } from '../vis/components/config/SelectionConfig';
import { SchemaSettings } from '../schema/panel/SchemaSettings';
import { QuerySettings } from '../querybuilder/panel/querysidepanel/QuerySettings';
import { useActiveVisualization } from '@graphpolaris/shared/lib/data-access';
import { getEnvVariable } from 'config';
export function InspectorPanel(props: { children?: React.ReactNode }) {
const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION;
const buildInfo = getEnvVariable('GRAPHPOLARIS_VERSION') === 'prod';
const selection = useSelection();
const focus = useFocus();
const dispatch = useDispatch();
......
......@@ -7,7 +7,7 @@ import {
useQuerybuilderAttributesShown,
useQuerybuilderGraph,
useQuerybuilderHash,
useQuerybuilderUnionTypes,
useQuerybuilderSettings
} from '../..';
import { isEqual } from 'lodash-es';
import {
......@@ -31,6 +31,8 @@ export const ContextMenu = (props: {
const dispatch = useAppDispatch();
const graph = useQuerybuilderGraph();
const qbHash = useQuerybuilderHash();
const settings = useQuerybuilderSettings();
const unionType = settings.unionTypes == null || props.node == null ? QueryUnionType.AND : settings.unionTypes[props.node.id];
const graphologyGraph = useMemo(() => toQuerybuilderGraphology(graph), [graph, qbHash]);
......@@ -69,7 +71,6 @@ export const ContextMenu = (props: {
dispatch(attributeShownToggle(attribute.handleData));
}
const unionType = useQuerybuilderUnionTypes();
function setUnionType(unionType: QueryUnionType) {
if (!props.node) return;
dispatch(setQueryUnionType({ nodeId: props.node.id, unionType: unionType }));
......@@ -125,12 +126,12 @@ export const ContextMenu = (props: {
<DropdownItem
value="AND"
onClick={(_) => setUnionType(QueryUnionType.AND)}
selected={props.node ? unionType[props.node.id] != QueryUnionType.OR : false} // Also selected when null
selected={props.node ? unionType != QueryUnionType.OR : false} // Also selected when null
/>,
<DropdownItem
value="OR"
onClick={(_) => setUnionType(QueryUnionType.OR)}
selected={props.node ? unionType[props.node.id] == QueryUnionType.OR : false}
selected={props.node ? unionType == QueryUnionType.OR : false}
/>,
]}
/>
......
......@@ -504,6 +504,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
} catch (e) {
console.error(e);
}
setContextMenuOpen({ ...contextMenuOpen, open: false });
}, [queryBuilderSettings]);
const handleScreenshot = async () => {
......
......@@ -262,7 +262,7 @@ export const QueryBuilderNav = (props: QueryBuilderNavProps) => {
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger>
<PopoverTrigger disabled={!saveStateAuthorization.query.W}>
<Tooltip>
<TooltipTrigger>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent="icon-[ic--baseline-search]" />
......
......@@ -8,6 +8,7 @@ import {
} from '@graphpolaris/shared/lib/data-access/store/mlSlice';
import { FormCard, FormBody, FormTitle, FormHBar } from '@graphpolaris/shared/lib/components/forms';
import { Input } from '@graphpolaris/shared/lib/components/inputs';
import { FeatureEnabled } from '@graphpolaris/shared/lib/components/featureFlags';
export const QueryMLDialog = () => {
const dispatch = useAppDispatch();
......@@ -25,51 +26,59 @@ export const QueryMLDialog = () => {
<FormHBar />
<div className="px-5">
<Input
type="boolean"
label="Link Prediction"
value={ml.linkPrediction.enabled}
onChange={() => dispatch(setLinkPredictionEnabled(!ml.linkPrediction.enabled))}
/>
{ml.linkPrediction.enabled && ml.linkPrediction.result && <span># of predictions: {ml.linkPrediction.result.length}</span>}
{ml.linkPrediction.enabled && !ml.linkPrediction.result && <span>Loading...</span>}
<FeatureEnabled featureFlag="LINK_PREDICTION">
<Input
type="boolean"
label="Link Prediction"
value={ml.linkPrediction.enabled}
onChange={() => dispatch(setLinkPredictionEnabled(!ml.linkPrediction.enabled))}
/>
{ml.linkPrediction.enabled && ml.linkPrediction.result && <span># of predictions: {ml.linkPrediction.result.length}</span>}
{ml.linkPrediction.enabled && !ml.linkPrediction.result && <span>Loading...</span>}
</FeatureEnabled>
<Input
type="boolean"
label="Centrality"
value={ml.centrality.enabled}
onChange={() => dispatch(setCentralityEnabled(!ml.centrality.enabled))}
/>
{ml.centrality.enabled && Object.values(ml.centrality.result).length > 0 && (
<span>
sum of centers:
{Object.values(ml.centrality.result)
.reduce((a, b) => b + a)
.toFixed(2)}
</span>
)}
{ml.centrality.enabled && Object.values(ml.centrality.result).length === 0 && <span>No Centers Found</span>}
<FeatureEnabled featureFlag="CENTRALITY">
<Input
type="boolean"
label="Centrality"
value={ml.centrality.enabled}
onChange={() => dispatch(setCentralityEnabled(!ml.centrality.enabled))}
/>
{ml.centrality.enabled && Object.values(ml.centrality.result).length > 0 && (
<span>
sum of centers:
{Object.values(ml.centrality.result)
.reduce((a, b) => b + a)
.toFixed(2)}
</span>
)}
{ml.centrality.enabled && Object.values(ml.centrality.result).length === 0 && <span>No Centers Found</span>}
</FeatureEnabled>
<Input
type="boolean"
label="Community detection"
value={ml.communityDetection.enabled}
onChange={() => dispatch(setCommunityDetectionEnabled(!ml.communityDetection.enabled))}
/>
{ml.communityDetection.enabled && ml.communityDetection.result && (
<span># of communities: {ml.communityDetection.result.length}</span>
)}
{ml.communityDetection.enabled && !ml.communityDetection.result && <span>Loading...</span>}
<FeatureEnabled featureFlag="COMMUNITY_DETECTION">
<Input
type="boolean"
label="Community detection"
value={ml.communityDetection.enabled}
onChange={() => dispatch(setCommunityDetectionEnabled(!ml.communityDetection.enabled))}
/>
{ml.communityDetection.enabled && ml.communityDetection.result && (
<span># of communities: {ml.communityDetection.result.length}</span>
)}
{ml.communityDetection.enabled && !ml.communityDetection.result && <span>Loading...</span>}
</FeatureEnabled>
<Input
type="boolean"
label="Shortest path"
value={ml.shortestPath.enabled}
onChange={() => dispatch(setShortestPathEnabled(!ml.shortestPath.enabled))}
/>
{ml.shortestPath.enabled && ml.shortestPath.result?.length > 0 && <span># of hops: {ml.shortestPath.result.length}</span>}
{ml.shortestPath.enabled && !ml.shortestPath.srcNode && <span>Please select source node</span>}
{ml.shortestPath.enabled && ml.shortestPath.srcNode && !ml.shortestPath.trtNode && <span>Please select target node</span>}
<FeatureEnabled featureFlag="SHORTEST_PATH">
<Input
type="boolean"
label="Shortest path"
value={ml.shortestPath.enabled}
onChange={() => dispatch(setShortestPathEnabled(!ml.shortestPath.enabled))}
/>
{ml.shortestPath.enabled && ml.shortestPath.result?.length > 0 && <span># of hops: {ml.shortestPath.result.length}</span>}
{ml.shortestPath.enabled && !ml.shortestPath.srcNode && <span>Please select source node</span>}
{ml.shortestPath.enabled && ml.shortestPath.srcNode && !ml.shortestPath.trtNode && <span>Please select target node</span>}
</FeatureEnabled>
</div>
</FormBody>
</FormCard>
......
......@@ -2,7 +2,7 @@ import {
useQuerybuilderAttributesShown,
useQuerybuilderGraph,
useQuerybuilderHash,
useQuerybuilderUnionTypes,
useQuerybuilderSettings,
} from '@graphpolaris/shared/lib/data-access';
import {
setQuerybuilderGraphology,
......@@ -38,9 +38,10 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => {
() => graph.edges.filter((edge) => edge.source === node.id && !!edge?.attributes?.sourceHandleData.attributeType),
[graph],
);
const settings = useQuerybuilderSettings();
const uniqueAttributes = useMemo(() => uniqBy(data.attributes, (attr) => attr.handleData.attributeName), [data.attributes]);
const unionType = useQuerybuilderUnionTypes()[node.id];
const unionType = settings.unionTypes == null ? QueryUnionType.AND : settings.unionTypes[node.id];
return (
<div className="w-fit h-fit nowheel" ref={ref} id="asd">
......
......@@ -4,13 +4,13 @@ import {
useAppDispatch,
useQuerybuilderGraph,
useQuerybuilderSettings,
useQuerybuilderUnionTypes,
} from '@graphpolaris/shared/lib/data-access';
import { addWarning } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import {
setQuerybuilderGraphology,
toQuerybuilderGraphology,
attributeShownToggle,
QueryUnionType
} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { PillAttributes } from '../../pillattributes/PillAttributes';
import {
......@@ -91,7 +91,7 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => {
dispatch(attributeShownToggle(attribute.handleData));
}
const unionType = useQuerybuilderUnionTypes()[node.id];
const unionType = settings.unionTypes == null ? QueryUnionType.AND : settings.unionTypes[node.id];
const attributesBeingShown = useQuerybuilderAttributesShown();
function isAttributeAdded(attribute: NodeAttribute): boolean {
......
......@@ -64,6 +64,9 @@ export function schemaGraphology2Reactflow(
export function createReactFlowNodes(graph: Graph): Array<Node> {
const nodeElements: Array<Node> = [];
graph.forEachNode((node: string, attributes: Attributes): void => {
if (!Array.isArray(attributes.attributes)) {
attributes.attributes = Object.values(attributes.attributes);
}
const newNode: Node = {
id: node,
data: {
......@@ -74,7 +77,6 @@ export function createReactFlowNodes(graph: Graph): Array<Node> {
};
nodeElements.push(newNode);
});
return nodeElements;
}
......
import React from 'react';
import { Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../components';
import ColorMode from '../components/color-mode';
import { FeatureEnabled } from '@graphpolaris/shared/lib/components/featureFlags';
export type SideNavTab = 'Schema' | 'Search' | undefined;
......@@ -29,9 +30,6 @@ export function Sidebar({
tab: SideNavTab;
openMonitoringDialog: () => void;
}) {
const isProd = import.meta.env.GRAPHPOLARIS_VERSION === 'prod';
const isInsightSharingWIP = import.meta.env.WIP_INSIGHT_SHARING === 'true';
return (
<div className="side-bar w-fit h-full flex shrink">
<TooltipProvider delayDuration={100}>
......@@ -58,7 +56,7 @@ export function Sidebar({
</Tooltip>
))}
{(!isProd || (isProd && !isInsightSharingWIP)) && (
<FeatureEnabled featureFlag="INSIGHT_SHARING">
<Tooltip placement={'right'}>
<TooltipTrigger asChild>
<Button
......@@ -71,7 +69,7 @@ export function Sidebar({
</TooltipTrigger>
<TooltipContent>Insight Sharing</TooltipContent>
</Tooltip>
)}
</FeatureEnabled>
<div className="mt-auto mb-2">
<ColorMode />
......
......@@ -17,21 +17,21 @@ import { updateVisualization, addVisualization } from '../../data-access/store/v
import { VisualizationPropTypes, VISComponentType } from '../common';
import { ErrorBoundary } from '../../components/errorBoundary';
import { addError } from '../../data-access/store/configSlice';
import { isVisualizationReleased } from 'config';
import { canViewFeature } from '@graphpolaris/shared/lib/components/featureFlags/featureFlags';
type PromiseFunc = () => Promise<{ default: VISComponentType<any> }>;
export const Visualizations: Record<string, PromiseFunc> = {
...(isVisualizationReleased('TableVis') && { TableVis: () => import('../visualizations/tablevis/tablevis') }),
...(isVisualizationReleased('PaohVis') && { PaohVis: () => import('../visualizations/paohvis/paohvis') }),
...(isVisualizationReleased('RawJSONVis') && { RawJSONVis: () => import('../visualizations/rawjsonvis/rawjsonvis') }),
...(isVisualizationReleased('NodeLinkVis') && { NodeLinkVis: () => import('../visualizations/nodelinkvis/nodelinkvis') }),
...(isVisualizationReleased('MatrixVis') && { MatrixVis: () => import('../visualizations/matrixvis/matrixvis') }),
...(isVisualizationReleased('SemanticSubstratesVis') && {
...(canViewFeature('TABLEVIS') && { TableVis: () => import('../visualizations/tablevis/tablevis') }),
...(canViewFeature('PAOHVIS') && { PaohVis: () => import('../visualizations/paohvis/paohvis') }),
...(canViewFeature('RAWJSONVIS') && { RawJSONVis: () => import('../visualizations/rawjsonvis/rawjsonvis') }),
...(canViewFeature('NODELINKVIS') && { NodeLinkVis: () => import('../visualizations/nodelinkvis/nodelinkvis') }),
...(canViewFeature('MATRIXVIS') && { MatrixVis: () => import('../visualizations/matrixvis/matrixvis') }),
...(canViewFeature('SEMANTICSUBSTRATESVIS') && {
SemanticSubstratesVis: () => import('../visualizations/semanticsubstratesvis/semanticsubstratesvis'),
}),
...(isVisualizationReleased('MapVis') && { MapVis: () => import('../visualizations/mapvis/mapvis') }),
...(isVisualizationReleased('Vis0D') && { Vis0D: () => import('../visualizations/vis0D/Vis0D') }),
...(isVisualizationReleased('Vis1D') && { Vis1D: () => import('../visualizations/vis1D/Vis1D') }),
...(canViewFeature('MAPVIS') && { MapVis: () => import('../visualizations/mapvis/mapvis') }),
...(canViewFeature('VIS0D') && { Vis0D: () => import('../visualizations/Vis0D/Vis0D') }),
...(canViewFeature('VIS1D') && { Vis1D: () => import('../visualizations/vis1D/Vis1D') }),
};
export const VISUALIZATION_TYPES: string[] = Object.keys(Visualizations);
......@@ -136,7 +136,7 @@ export const VisualizationPanel = ({ fullSize }: { fullSize: () => void }) => {
</div>
)}
</div>
<VisualizationTabBar fullSize={fullSize} exportImage={exportImage} />
<VisualizationTabBar fullSize={fullSize} exportImage={exportImage} handleSelect={handleSelect} />
</div>
);
};
......@@ -7,7 +7,7 @@ import { useActiveSaveState, useActiveSaveStateAuthorization, useAppDispatch, us
import { addVisualization, removeVisualization, reorderVisState, setActiveVisualization } from '../../data-access/store/visualizationSlice';
import { Visualizations } from './VisualizationPanel';
export default function VisualizationTabBar(props: { fullSize: () => void; exportImage: () => void }) {
export default function VisualizationTabBar(props: { fullSize: () => void; exportImage: () => void; handleSelect: () => void }) {
const { activeVisualizationIndex, openVisualizationArray } = useVisualization();
const saveStateAuthorization = useActiveSaveStateAuthorization();
const [open, setOpen] = useState(false);
......@@ -34,6 +34,7 @@ export default function VisualizationTabBar(props: { fullSize: () => void; expor
const onDelete = (id: number) => {
dispatch(removeVisualization(id));
props.handleSelect();
};
useEffect(() => {
......
......@@ -56,15 +56,6 @@ export function VisualizationSettings({}: Props) {
<div className="text-sm px-4 py-2 flex flex-col gap-1">
<div className="flex justify-between items-center">
<span className="font-bold">Visualization</span>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-delete]"
onClick={() => {
if (activeVisualization) removeVisualization();
}}
/>
</div>
<Input
type="text"
......
......@@ -2,7 +2,7 @@ import { Edge, GraphQueryResult, Node, useML } from '@graphpolaris/shared/lib/da
import { dataColors, visualizationColors } from 'config';
import { Viewport } from 'pixi-viewport';
import { Application, ColorSource, Container, FederatedPointerEvent, Graphics, IPointData, Point, Text } from 'pixi.js';
import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react';
import { useEffect, useRef, useState, useMemo, useImperativeHandle, forwardRef } from 'react';
import { LinkType, NodeType } from '../types';
import { NLPopup } from './MatrixPopup';
......@@ -63,10 +63,11 @@ export const MatrixPixi = forwardRef((props: Props, refExternal) => {
const globalConfig = useConfig();
useEffect(() => {
if (props.graph) {
setup();
if (props.graph && internalRef.current && imperative.current) {
if (isSetup.current === false) setup();
else update();
}
}, [globalConfig.theme]);
}, [props.graph, globalConfig.theme]);
let columnOrder: string[] = [];
let rowOrder: string[] = [];
......@@ -82,6 +83,20 @@ export const MatrixPixi = forwardRef((props: Props, refExternal) => {
const isSetup = useRef(false);
const ml = useML();
const app = useMemo(
() =>
new Application({
backgroundAlpha: 0,
antialias: true,
autoDensity: true,
eventMode: 'auto',
resolution: window.devicePixelRatio || 2,
view: canvas.current as HTMLCanvasElement,
}),
[canvas.current],
);
useEffect(() => {
if (typeof refExternal === 'function') {
refExternal(internalRef.current);
......@@ -135,13 +150,11 @@ export const MatrixPixi = forwardRef((props: Props, refExternal) => {
},
}));
let app: Application;
function resize() {
const width = internalRef?.current?.clientWidth || 1000;
const height = internalRef?.current?.clientHeight || 1000;
app.renderer.resize(width, height);
app?.renderer.resize(width, height);
if (viewport.current) {
viewport.current.screenWidth = width;
viewport.current.worldWidth = width;
......@@ -152,22 +165,23 @@ export const MatrixPixi = forwardRef((props: Props, refExternal) => {
if (props.graph) {
setup();
}
app.render();
app?.render();
}
useEffect(() => {
if (props.graph && internalRef.current && internalRef.current.children.length > 0) {
if (!isSetup.current) setup();
else update();
return () => {
app.destroy();
}
}, [props.graph]);
}, []);
useEffect(() => {
if (props.graph && internalRef.current && internalRef.current.children.length > 0) {
setup();
}
}, [props.settings]);
if (!internalRef.current) return;
const resizeObserver = new ResizeObserver(() => {
resize();
});
resizeObserver.observe(internalRef.current);
return () => resizeObserver.disconnect();
}, []);
// TODO implement search results
// useEffect(() => {
......@@ -306,22 +320,6 @@ export const MatrixPixi = forwardRef((props: Props, refExternal) => {
const setup = () => {
if (!props.graph) throw Error('Graph is undefined; setup not possible');
if (app == null) {
app = new Application({
backgroundAlpha: 0,
antialias: true,
autoDensity: true,
eventMode: 'auto',
resolution: window.devicePixelRatio || 1,
view: canvas.current as HTMLCanvasElement,
});
const resizeObserver = new ResizeObserver(() => {
resize();
});
resizeObserver.observe(internalRef.current as HTMLDivElement);
}
if (svg.current != null) {
select(svg.current).selectAll('*').remove();
}
......@@ -409,8 +407,9 @@ export const MatrixPixi = forwardRef((props: Props, refExternal) => {
viewport.current.drag().pinch().wheel().animate({}).decelerate({ friction: 0.75 });
app.ticker.add(tick);
update();
isSetup.current = true;
update();
};
let scaleColumns: ScaleBand<string>;
......
......@@ -774,10 +774,7 @@ export const NLPixi = forwardRef((props: Props, refExternal) => {
return () => {
nodeMap.current.clear();
linkLabelMap.current.clear();
linkGfx.clear();
nodeLayer.removeChildren();
linkLabelLayer.removeChildren();
nodeLabelLayer.removeChildren();
app.destroy();
const layout = layoutAlgorithm.current as GraphologyForceAtlas2Webworker;
if (layout?.cleanup != null) layout.cleanup();
......
......@@ -10,7 +10,7 @@ export function nodeColor(num: number) {
// num = num % 4;
// const col = '#000000';
//let entityColors = Object.values(visualizationColors.GPSeq.colors[9]);
const col = visualizationColors.GPCat.colors[14][num % visualizationColors.GPCat.colors[14].length];
const col = visualizationColors.GPCat.colors[14][(num - 1) % visualizationColors.GPCat.colors[14].length];
return binaryColor(col);
}
......@@ -19,7 +19,7 @@ export function nodeColorHex(num: number) {
// const col = '#000000';
//let entityColors = Object.values(visualizationColors.GPSeq.colors[9]);
const col = visualizationColors.GPCat.colors[14][num % visualizationColors.GPCat.colors[14].length];
const col = visualizationColors.GPCat.colors[14][(num - 1) % visualizationColors.GPCat.colors[14].length];
return col;
}
......