From e506336f1f42c8907e3bc77f7a28a9814dc88919 Mon Sep 17 00:00:00 2001 From: Dor Meiri <37194716+dormeiri@users.noreply.github.com> Date: Tue, 21 Mar 2023 23:46:27 +0200 Subject: [PATCH] fix import button (#51) --- packages/ui/src/App.tsx | 53 ++++++++++++++++-------------- packages/ui/src/VisNetwork.tsx | 21 +++++------- packages/ui/src/scanOutputStore.ts | 13 ++++++-- 3 files changed, 48 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 31d1484..42d60e1 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -13,13 +13,14 @@ import { Select } from './Select'; import { Toggle } from './Toggle'; import { VisNetwork } from './VisNetwork'; import { scanOutputStore } from './scanOutputStore'; -import type { ScanResultExtended, SelectOption, FilterOption } from './types'; +import type { SelectOption, FilterOption } from './types'; import { everyIncludes, produceNewRelationship } from './utils'; export function App() { + const [isImporting, setIsImporting] = useState(true); + const [withDiff, setWithDiff] = useState(true); - const [scanOutput, setScanOutput] = useState(); const [tags, setTags] = useState[]>([]); const [resources, setResources] = useState[]>([]); const [selectedResourceId, setSelectedResourceId] = useState(); @@ -27,10 +28,14 @@ export function App() { const selectedTags = tags.filter((tag) => tag.selected); const selectedTagValues = selectedTags.map((tag) => tag.value); - const selectedResource = selectedResourceId ? scanOutput?.resources.find((r) => r.id === selectedResourceId) : undefined; + const selectedResource = selectedResourceId ? scanOutputStore.scanOutput?.resources.find((r) => r.id === selectedResourceId) : undefined; useEffect(() => { - scanOutputStore.importBundledScanOutput().then(syncWithStore); + (async () => { + await scanOutputStore.importBundledScanOutput(); + setIsImporting(false); + syncWithStore(); + })(); }, []); useEffect(() => { @@ -39,15 +44,20 @@ export function App() { } }, [tags]); + async function importScanOutput() { + await scanOutputStore.import(() => setIsImporting(true)); + setIsImporting(false); + syncWithStore(); + } + function syncWithStore() { - setScanOutput(JSON.parse(JSON.stringify(scanOutputStore.scanOutput))); setTags(scanOutputStore.extractTagOptions()); setResources(scanOutputStore.extractResourceOptions()); } function handleTagPillClick(clickedTag: SelectOption): void { const newTags = JSON.parse(JSON.stringify(tags)); - newTags.find((tag) => tag.value === clickedTag.value).selected = false; + newTags.find((tag: SelectOption) => tag.value === clickedTag.value).selected = false; setTags(newTags); } @@ -64,8 +74,8 @@ export function App() { } function handleNewResource(resource: Resource): void { - const extendedResource = scanOutputStore.addResource(resource); - VisNetwork.addResource(extendedResource); + const extendedResource = scanOutputStore.addNode(resource); + VisNetwork.addNode(extendedResource); syncWithStore(); closeResourceEditModal(); } @@ -77,12 +87,15 @@ export function App() { for (const relationship of resource.relationships ?? []) { if ((relationship.diff ?? resource.diff) === '+') { resource.relationships = resource.relationships?.filter((r) => r.resourceId !== relationship.resourceId); - VisNetwork.removeRelationship(resourceId, relationship.resourceId); + VisNetwork.removeEdge(resourceId, relationship.resourceId); } else { - VisNetwork.updateRelationship(resource, relationship); + VisNetwork.syncEdgeStyle(resource, relationship); } } - } else VisNetwork.removeResource(resourceId); + } else { + VisNetwork.removeNode(resourceId); + setSelectedResourceId(undefined); + } syncWithStore(); } @@ -104,15 +117,15 @@ export function App() { if (!relationship) throw new Error('Invalid relationship'); if ((relationship.diff ?? resource.diff) === '+') { resource.relationships = resource.relationships?.filter((r) => r.resourceId !== relationshipResourceId); - VisNetwork.removeRelationship(resourceId, relationshipResourceId); + VisNetwork.removeEdge(resourceId, relationshipResourceId); } else { relationship.diff = '-'; - VisNetwork.updateRelationship(resource, relationship); + VisNetwork.syncEdgeStyle(resource, relationship); } syncWithStore(); } - return scanOutput == null ? ( + return isImporting ? (
Loading...
) : (
@@ -125,7 +138,7 @@ export function App() {
@@ -147,15 +160,7 @@ export function App() { )}
- {scanOutput && ( - - )} +
diff --git a/packages/ui/src/VisNetwork.tsx b/packages/ui/src/VisNetwork.tsx index f3c63c6..7cca245 100644 --- a/packages/ui/src/VisNetwork.tsx +++ b/packages/ui/src/VisNetwork.tsx @@ -5,6 +5,7 @@ import { DataSet, DataView } from 'vis-data'; import { Network } from 'vis-network'; import { getTypeImagePath } from './constants'; +import { scanOutputStore } from './scanOutputStore'; import type { Diff, RelationshipExtended, ResourceExtended } from './types'; import { everyIncludes } from './utils'; @@ -55,9 +56,6 @@ interface Group { } export interface VisNetworkProps { - scanOutput: { - resources: ResourceExtended[]; - }; selectedTags: string[]; selectedResourceId: string | undefined; resourceSelected: (nodeId: string | undefined) => void; @@ -72,7 +70,7 @@ export class VisNetwork extends React.Component { private static nodes: DataView; private static network?: Network; - public static addResource(resource: ResourceExtended) { + public static addNode(resource: ResourceExtended) { const node = produceNode(resource); if (node.group && !VisNetwork.groups[node.group]) { node.shape = 'image'; @@ -88,7 +86,7 @@ export class VisNetwork extends React.Component { VisNetwork.nodes.getDataSet().update(produceNode(resource)); } - public static removeResource(resourceId: string) { + public static removeNode(resourceId: string) { VisNetwork.nodes.getDataSet().remove(resourceId); } @@ -101,14 +99,14 @@ export class VisNetwork extends React.Component { VisNetwork.edges.getDataSet().add(edge); } - public static updateRelationship(resource: ResourceExtended, relationship: RelationshipExtended) { + public static syncEdgeStyle(resource: ResourceExtended, relationship: RelationshipExtended) { if (resource.diff ?? relationship.diff) { const existingEdge = VisNetwork.edges.get(produceEdgeId(resource.id, relationship.resourceId)); VisNetwork.edges.getDataSet().update({ ...existingEdge, color: produceEdgeColor(resource.diff, relationship.diff) }); } } - public static removeRelationship(resourceId: string, relationshipResourceId: string) { + public static removeEdge(resourceId: string, relationshipResourceId: string) { VisNetwork.edges.getDataSet().remove(produceEdgeId(resourceId, relationshipResourceId)); } @@ -159,14 +157,14 @@ export class VisNetwork extends React.Component { } private extractNodes(): DataView { - return new DataView(new DataSet(this.props.scanOutput.resources.map(produceNode)), { + return new DataView(new DataSet(scanOutputStore.scanOutput.resources.map(produceNode)), { filter: (node) => (this.props.withDiff || node.diff == null) && everyIncludes(this.props.selectedTags, node.tags), }); } private extractEdges(): DataView { const edges: Record = {}; - for (const resource of this.props.scanOutput.resources) { + for (const resource of scanOutputStore.scanOutput.resources) { for (const relationship of resource.relationships ?? []) { const id = produceEdgeId(resource.id, relationship.resourceId); edges[id] ??= produceEdge(resource, relationship); @@ -198,10 +196,7 @@ export class VisNetwork extends React.Component { for (const edge of VisNetwork.network.getConnectedEdges(this.props.selectedResourceId)) { VisNetwork.network.updateEdge(edge, { width: edgeWidth.selected }); } - VisNetwork.network.focus(this.props.selectedResourceId, { - animation: true, - scale: 1, - }); + VisNetwork.network.focus(this.props.selectedResourceId, { animation: true, scale: 1 }); } } diff --git a/packages/ui/src/scanOutputStore.ts b/packages/ui/src/scanOutputStore.ts index 82ec33e..61927a5 100644 --- a/packages/ui/src/scanOutputStore.ts +++ b/packages/ui/src/scanOutputStore.ts @@ -3,7 +3,11 @@ import type { Resource } from '@noodle-graph/types'; import { ResourceAlreadyExistError } from './errors'; import type { ResourceExtended, ScanResultExtended, SelectOption, FilterOption } from './types'; +type ImportState = 'importing' | 'imported' | undefined; + class ScanOutputStore { + private _importState: ImportState; + // If there is any performance issue, we can store the resources as object instead of array. private firstScanOutput: ScanResultExtended = { resources: [] }; private _scanOutput: ScanResultExtended = { resources: [] }; @@ -12,6 +16,10 @@ class ScanOutputStore { return this._scanOutput; } + public get importState(): ImportState { + return this._importState; + } + public download(): void { const blob = new Blob([JSON.stringify(this._scanOutput)], { type: 'application/json' }); const url = window.URL.createObjectURL(blob); @@ -22,7 +30,7 @@ class ScanOutputStore { window.URL.revokeObjectURL(url); } - public import(): Promise { + public import(onImportStarted: () => void): Promise { return new Promise((resolve) => { const input = document.createElement('input'); input.type = 'file'; @@ -32,6 +40,7 @@ class ScanOutputStore { if (!file) { return; } + onImportStarted(); const reader = new FileReader(); reader.onload = (e) => { if (e.target?.result == null) return; @@ -83,7 +92,7 @@ class ScanOutputStore { } } - public addResource(resource: Resource): ResourceExtended { + public addNode(resource: Resource): ResourceExtended { if (this._scanOutput.resources.some((r) => r.id === resource.id)) throw new ResourceAlreadyExistError(); const newResource: ResourceExtended = { ...resource, source: 'ui', diff: '+' };