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
Commits on Source (14)
Showing
with 208 additions and 131 deletions
......@@ -6,7 +6,6 @@ import {
useQuerybuilderGraph,
useQuerybuilderSettings,
useSessionCache,
useQuerybuilderUnionTypes,
} from '@graphpolaris/shared/lib/data-access';
import { addError, setCurrentTheme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { resetGraphQueryResults, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
......@@ -39,7 +38,6 @@ export function App(props: App) {
const dispatch = useAppDispatch();
const queryBuilderSettings = useQuerybuilderSettings();
const [monitoringOpen, setMonitoringOpen] = useState<boolean>(false);
const unionTypes = useQuerybuilderUnionTypes();
const runQuery = () => {
if (session?.currentSaveState && query) {
......@@ -47,7 +45,7 @@ export function App(props: App) {
dispatch(resetGraphQueryResults());
} else {
dispatch(queryingBackend());
wsQueryRequest(Query2BackendQuery(session.currentSaveState, query, queryBuilderSettings, ml, unionTypes));
wsQueryRequest(Query2BackendQuery(session.currentSaveState, query, queryBuilderSettings, ml, queryBuilderSettings.unionTypes));
}
}
};
......
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { Button } from '../buttons';
import { useAppDispatch } from '@graphpolaris/shared/lib/data-access';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
dispatch?: ReturnType<typeof useAppDispatch>;
}
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
errorInfo?: ErrorInfo;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
this.handleRetry = this.handleRetry.bind(this);
this.handleCopyError = this.handleCopyError.bind(this);
}
static getDerivedStateFromError(): ErrorBoundaryState {
......@@ -22,20 +29,48 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState({ error, errorInfo });
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}
handleRetry() {
this.setState({ hasError: false });
this.setState({ hasError: false, error: undefined, errorInfo: undefined });
}
handleCopyError() {
const errorText = `Error: ${this.state.error?.toString()}\n\nStack trace: ${this.state.errorInfo?.componentStack}`;
navigator.clipboard.writeText(errorText);
if (this.props.dispatch) {
this.props.dispatch(addInfo('Stack trace copied to clipboard'));
}
}
render() {
if (this.state.hasError) {
return (
<div>
<div className="flex flex-col w-full h-full">
{this.props.fallback || <div>Something went wrong. Please try again later.</div>}
{import.meta.env.GRAPHPOLARIS_VERSION === 'dev' && (
<div className="overflow-auto max-h-[500px] p-2">
<div className="flex justify-end mb-2">
<Button
label="Copy Stack Trace"
variant="outline"
variantType="secondary"
size="xs"
onClick={this.handleCopyError}
/>
</div>
<pre className="whitespace-pre-wrap break-words text-sm">
{this.state.error?.toString()}
</pre>
<pre className="whitespace-pre-wrap break-words text-sm mt-2">
{this.state.errorInfo?.componentStack}
</pre>
</div>
)}
<Button label="Retry now" variant="outline" variantType="primary" onClick={this.handleRetry} />
</div>
);
......@@ -45,4 +80,9 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
}
}
export { ErrorBoundary };
function ErrorBoundaryWithDispatch(props: Omit<ErrorBoundaryProps, 'dispatch'>) {
const dispatch = useAppDispatch();
return <ErrorBoundary {...props} dispatch={dispatch} />;
}
export { ErrorBoundaryWithDispatch as ErrorBoundary };
\ No newline at end of file
......@@ -64,9 +64,9 @@ export const TableUI = <T extends Record<string, any>>({ data, fieldConfigs, dro
return (
<div className="mt-2 w-full overflow-x-auto">
<table className="min-w-full bg-white border border-gray-300 rounded-md">
<table className="min-w-full bg-light border border-secondary-300 rounded-md">
<thead>
<tr className="bg-gray-100 border-b">
<tr className="bg-secondary-100 border-b">
{fieldConfigs.map((field) => (
<th key={field.key.toString()} className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
{field.label}
......@@ -79,7 +79,7 @@ export const TableUI = <T extends Record<string, any>>({ data, fieldConfigs, dro
{data.map((item, index) => (
<tr key={index} className="border-b rounded-md">
{fieldConfigs.map((field) => (
<td key={field.key.toString()} className="px-6 py-4 text-sm text-gray-700">
<td key={field.key.toString()} className="px-6 py-4 text-sm text-light-700">
{editingIndex === index ? (
field.type === 'dropdown' ? (
<Input
......@@ -103,7 +103,7 @@ export const TableUI = <T extends Record<string, any>>({ data, fieldConfigs, dro
)}
</td>
))}
<td className="px-6 py-4 text-sm text-gray-700">
<td className="px-6 py-4 text-sm text-secondary-700">
<div className="flex space-x-2">
{editingIndex === index ? (
<>
......
......@@ -14,14 +14,12 @@ import {
wsSchemaSubscription,
useQuerybuilderAttributesShown,
wsSchemaStatsRequest,
useQuerybuilderUnionTypes,
} from '@graphpolaris/shared/lib/data-access';
import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { allMLTypes, LinkPredictionInstance, setMLResult } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
import {
QueryBuilderText,
queryUnionTypes,
setQueryText,
setQuerybuilderNodes,
} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
......@@ -70,7 +68,6 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
const mlHash = useMLEnabledHash();
const visState = useVisualization();
const queryBuilderSettings = useQuerybuilderSettings();
const unionTypes = useQuerybuilderUnionTypes();
function loadSaveState(saveStateID: string | undefined, saveStates: Record<string, SaveStateI>) {
if (saveStateID && saveStates && saveStateID in saveStates) {
......@@ -291,7 +288,7 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
// body: { type: 'query_builder_state', status: '', value: queryBuilder },
// });
}
}, [queryHash, mlHash, queryBuilderSettings, unionTypes]);
}, [queryHash, mlHash, queryBuilderSettings, queryBuilderSettings.unionTypes]);
return <div className="hide"></div>;
};
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;
......@@ -16,15 +17,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 +37,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
......@@ -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]" />
......
......@@ -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;
}
......
......@@ -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;
}
......