Skip to content

Commit

Permalink
using atom/action pattern for import/export/layout #102
Browse files Browse the repository at this point in the history
  • Loading branch information
sim51 committed Jan 30, 2024
1 parent 3401a2f commit 90a6828
Show file tree
Hide file tree
Showing 22 changed files with 300 additions and 298 deletions.
6 changes: 3 additions & 3 deletions src/core/Initialize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { extractFilename } from "../utils/url";
import { WelcomeModal } from "../views/graphPage/modals/WelcomeModal";
import { appearanceAtom } from "./appearance";
import { parseAppearanceState } from "./appearance/utils";
import { useFileActions } from "./context/dataContexts";
import { useImportActions } from "./context/dataContexts";
import { filtersAtom } from "./filters";
import { parseFiltersState } from "./filters/utils";
import { graphDatasetAtom } from "./graph";
Expand All @@ -30,7 +30,7 @@ export const Initialize: FC<PropsWithChildren<unknown>> = ({ children }) => {
const { t } = useTranslation();
const { notify } = useNotifications();
const { openModal } = useModal();
const { openRemoteFile } = useFileActions();
const { importRemoteGexf } = useImportActions();

useKonami(
() => {
Expand Down Expand Up @@ -96,7 +96,7 @@ export const Initialize: FC<PropsWithChildren<unknown>> = ({ children }) => {
if (!graphFound && url.searchParams.has("gexf")) {
const gexfUrl = url.searchParams.get("gexf") || "";
try {
await openRemoteFile({
await importRemoteGexf({
type: "remote",
filename: extractFilename(gexfUrl),
url: gexfUrl,
Expand Down
2 changes: 1 addition & 1 deletion src/core/cloud/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GraphFile } from "../graph/types";
import { GraphFile } from "../graph/import/types";

export interface CloudFile extends GraphFile {
type: "cloud";
Expand Down
17 changes: 10 additions & 7 deletions src/core/cloud/useCloudProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import { parse } from "graphology-gexf";
import { isNil } from "lodash";
import { useCallback, useState } from "react";

import { useExportActions } from "../context/dataContexts";
import { graphDatasetAtom } from "../graph";
import { useExportAsGexf } from "../graph/useExportAsGexf";
import { initializeGraphDataset } from "../graph/utils";
import { resetCamera } from "../sigma";
import { useConnectedUser } from "../user/index";
import { useAtom } from "../utils/atoms";
import { CloudFile } from "./types";

// TODO: need to be refaco by atom/action/producer pattern
export function useCloudProvider() {
const [user] = useConnectedUser();
const { exportAsGexf } = useExportAsGexf();
const [graphDataset, setGraphDataset] = useAtom(graphDatasetAtom);
const { exportAsGexf } = useExportActions();

const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<Error | null>(null);
Expand Down Expand Up @@ -67,8 +68,9 @@ export function useCloudProvider() {
try {
if (isNil(user)) throw new Error("You must be logged !");
if (!graphDataset.origin || graphDataset.origin.type !== "cloud") throw new Error("Not a cloud graph");
const content = exportAsGexf();
await user.provider.saveFile(graphDataset.origin, content);
await exportAsGexf(async (content) => {
await user.provider.saveFile(graphDataset.origin as CloudFile, content);
});
} catch (e) {
setError(e as Error);
throw e;
Expand All @@ -86,9 +88,10 @@ export function useCloudProvider() {
setError(null);
try {
if (isNil(user)) throw new Error("You must be logged !");
const content = exportAsGexf();
const result = await user.provider.createFile(file, content);
setGraphDataset({ ...graphDataset, origin: result });
await exportAsGexf(async (content) => {
const result = await user.provider.createFile(file, content);
setGraphDataset({ ...graphDataset, origin: result });
});
} catch (e) {
setError(e as Error);
throw e;
Expand Down
20 changes: 15 additions & 5 deletions src/core/context/dataContexts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { Context, FC, ReactNode, createContext, useContext } from "react";
import { appearanceActions, appearanceAtom } from "../appearance";
import { filtersActions, filtersAtom } from "../filters";
import { filteredGraphAtom, graphDatasetActions, graphDatasetAtom, sigmaGraphAtom, visualGettersAtom } from "../graph";
import { fileActions, fileStateAtom } from "../graph/files";
import { exportActions, exportStateAtom } from "../graph/export";
import { importActions, importStateAtom } from "../graph/import";
import { layoutActions, layoutStateAtom } from "../layouts";
import { preferencesActions, preferencesAtom } from "../preferences";
import { searchActions, searchAtom } from "../search";
import { selectionActions, selectionAtom } from "../selection";
Expand Down Expand Up @@ -40,7 +42,8 @@ const ATOMS = {
sigma: sigmaAtom,
filters: filtersAtom,
selection: selectionAtom,
fileState: fileStateAtom,
importState: importStateAtom,
exportState: exportStateAtom,
appearance: appearanceAtom,
sigmaState: sigmaStateAtom,
sigmaGraph: sigmaGraphAtom,
Expand All @@ -49,14 +52,16 @@ const ATOMS = {
filteredGraph: filteredGraphAtom,
visualGetters: visualGettersAtom,
search: searchAtom,
layoutState: layoutStateAtom,
};
type AtomName = keyof typeof ATOMS;

const CONTEXTS = {
sigma: createContext(ATOMS.sigma),
filters: createContext(ATOMS.filters),
selection: createContext(ATOMS.selection),
fileState: createContext(ATOMS.fileState),
importState: createContext(ATOMS.importState),
exportState: createContext(ATOMS.exportState),
appearance: createContext(ATOMS.appearance),
sigmaState: createContext(ATOMS.sigmaState),
sigmaGraph: createContext(ATOMS.sigmaGraph),
Expand All @@ -65,6 +70,7 @@ const CONTEXTS = {
filteredGraph: createContext(ATOMS.filteredGraph),
visualGetters: createContext(ATOMS.visualGetters),
search: createContext(ATOMS.search),
layoutState: createContext(ATOMS.layoutState),
};

/**
Expand Down Expand Up @@ -96,7 +102,8 @@ export const resetStates: Action = () => {
export const useFilters = makeUseAtom(CONTEXTS.filters);
export const useSigmaAtom = makeUseAtom(CONTEXTS.sigma);
export const useSelection = makeUseAtom(CONTEXTS.selection);
export const useFileState = makeUseAtom(CONTEXTS.fileState);
export const useImportState = makeUseAtom(CONTEXTS.importState);
export const useExportState = makeUseAtom(CONTEXTS.exportState);
export const useAppearance = makeUseAtom(CONTEXTS.appearance);
export const useSigmaState = makeUseAtom(CONTEXTS.sigmaState);
export const useSigmaGraph = makeUseAtom(CONTEXTS.sigmaGraph);
Expand All @@ -105,6 +112,7 @@ export const useGraphDataset = makeUseAtom(CONTEXTS.graphDataset);
export const useFilteredGraph = makeUseAtom(CONTEXTS.filteredGraph);
export const useVisualGetters = makeUseAtom(CONTEXTS.visualGetters);
export const useSearch = makeUseAtom(CONTEXTS.search);
export const useLayoutState = makeUseAtom(CONTEXTS.layoutState);

export const useSigmaActions = makeUseActions(sigmaActions);
export const useFiltersActions = makeUseActions(filtersActions);
Expand All @@ -113,7 +121,9 @@ export const useAppearanceActions = makeUseActions(appearanceActions);
export const useGraphDatasetActions = makeUseActions(graphDatasetActions);
export const usePreferencesActions = makeUseActions(preferencesActions);
export const useSearchActions = makeUseActions(searchActions);
export const useFileActions = makeUseActions(fileActions);
export const useImportActions = makeUseActions(importActions);
export const useExportActions = makeUseActions(exportActions);
export const useLayoutActions = makeUseActions(layoutActions);

export const useResetStates = () => {
return resetStates;
Expand Down
56 changes: 56 additions & 0 deletions src/core/graph/export/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { write } from "graphology-gexf";
import { toUndirected } from "graphology-operators";

import { filteredGraphAtom, graphDatasetAtom, visualGettersAtom } from "..";
import { applyVisualProperties } from "../../appearance/utils";
import { atom } from "../../utils/atoms";
import { asyncAction } from "../../utils/producers";
import { dataGraphToFullGraph } from "../utils";
import { ExportState } from "./types";

function getEmptyExportState(): ExportState {
return { type: "idle" };
}

/**
* Public API:
* ***********
*/
export const exportStateAtom = atom<ExportState>(getEmptyExportState());

/**
* Actions:
* ********
*/
export const exportAsGexf = asyncAction(async (callback: (content: string) => void | Promise<void>) => {
// set loading
exportStateAtom.set({ type: "loading" });

try {
// get the full graph
const graphDataset = graphDatasetAtom.get();
const filteredGraph = filteredGraphAtom.get();
let graphToExport = dataGraphToFullGraph(graphDataset, filteredGraph);

// apply current apperanc eon the graph
const visualGetters = visualGettersAtom.get();
applyVisualProperties(graphToExport, graphDataset, visualGetters);

// change the type of the graph based on the meta type (default is directed)
if (graphDataset.metadata.type === "undirected") graphToExport = toUndirected(graphToExport);

// generate the gexf
const content = write(graphToExport, {});

await callback(content);

// idle state
exportStateAtom.set({ type: "idle" });
} catch (e) {
exportStateAtom.set({ type: "error" });
}
});

export const exportActions = {
exportAsGexf,
};
1 change: 1 addition & 0 deletions src/core/graph/export/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ExportState = { type: "idle" } | { type: "loading" } | { type: "error"; message?: string };
71 changes: 0 additions & 71 deletions src/core/graph/files.ts

This file was deleted.

75 changes: 75 additions & 0 deletions src/core/graph/import/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Graph from "graphology";
import { parse } from "graphology-gexf";

import { resetStates } from "../../context/dataContexts";
import { preferencesActions } from "../../preferences";
import { resetCamera } from "../../sigma";
import { atom } from "../../utils/atoms";
import { asyncAction } from "../../utils/producers";
import { graphDatasetActions } from "../index";
import { initializeGraphDataset } from "../utils";
import { ImportState, LocalFile, RemoteFile } from "./types";

function getEmptyImportState(): ImportState {
return { type: "idle" };
}

/**
* Public API:
* ***********
*/
export const importStateAtom = atom<ImportState>(getEmptyImportState());

/**
* Actions:
* ********
*/
export const importRemoteGexf = asyncAction(async (remote: RemoteFile) => {
const { setGraphDataset } = graphDatasetActions;
const { addRemoteFile } = preferencesActions;

if (importStateAtom.get().type === "loading") throw new Error("A file is already being loaded");

importStateAtom.set({ type: "loading" });
try {
const response = await fetch(remote.url);
const gexf = await response.text();
const graph = parse(Graph, gexf, { allowUndeclaredAttributes: true, addMissingNodes: true });
graph.setAttribute("title", remote.filename);
resetStates();
setGraphDataset({ ...initializeGraphDataset(graph), origin: remote });
addRemoteFile(remote);
resetCamera({ forceRefresh: true });
} catch (e) {
importStateAtom.set({ type: "error", message: (e as Error).message });
throw e;
} finally {
importStateAtom.set({ type: "idle" });
}
});

export const importLocalGexf = asyncAction(async (file: LocalFile) => {
const { setGraphDataset } = graphDatasetActions;

if (importStateAtom.get().type === "loading") throw new Error("A file is already being loaded");

importStateAtom.set({ type: "loading" });
try {
const content = await file.source.text();
const graph = parse(Graph, content, { allowUndeclaredAttributes: true });
graph.setAttribute("title", file.filename);
resetStates();
setGraphDataset({ ...initializeGraphDataset(graph), origin: file });
resetCamera({ forceRefresh: true });
} catch (e) {
importStateAtom.set({ type: "error", message: (e as Error).message });
throw e;
} finally {
importStateAtom.set({ type: "idle" });
}
});

export const importActions = {
importRemoteGexf,
importLocalGexf,
};
19 changes: 19 additions & 0 deletions src/core/graph/import/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { CloudFile } from "../../cloud/types";

export interface GraphFile {
type: string;
filename: string;
}
export interface RemoteFile extends GraphFile {
type: "remote";
url: string;
}
export interface LocalFile extends GraphFile {
type: "local";
updatedAt: Date;
size: number;
source: File;
}
export type GraphOrigin = CloudFile | RemoteFile | LocalFile | null;

export type ImportState = { type: "idle" } | { type: "loading" } | { type: "error"; message?: string };
Loading

0 comments on commit 90a6828

Please sign in to comment.