Skip to content

Commit

Permalink
Merge pull request #657 from tone-row/dev
Browse files Browse the repository at this point in the history
v1.48.0
  • Loading branch information
rob-gordon authored Mar 14, 2024
2 parents 621735d + 7c0c6f4 commit b646d30
Show file tree
Hide file tree
Showing 23 changed files with 471 additions and 204 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "1.47.6",
"version": "1.48.0",
"main": "module/module.js",
"license": "MIT",
"scripts": {
Expand Down
Binary file added app/public/templates/template21.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions app/src/components/ShareDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import Spinner from "./Spinner";
import { SvgProOnlyPopover } from "./SvgProOnlyPopover";
import { toExcalidraw } from "../lib/toExcalidraw";
import { Link } from "react-router-dom";
import { toJSONCanvas } from "../lib/toJSONCanvas";
import { slugify } from "../lib/helpers";

export default function ShareDialog({ children }: { children?: ReactNode }) {
const isHosted = useDocDetails("isHosted");
Expand Down Expand Up @@ -172,6 +174,12 @@ export default function ShareDialog({ children }: { children?: ReactNode }) {
>
<span>Excalidraw</span>
</Tabs.Trigger>
<Tabs.Trigger
value="json"
className="font-bold text-sm p-2 rounded data-[state=active]:bg-neutral-300 hover:bg-neutral-100 dark:data-[state=active]:bg-neutral-700 dark:hover:bg-neutral-800"
>
<span>JSON Canvas / Obsidian</span>
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="mermaid">
<Mermaid />
Expand All @@ -182,6 +190,9 @@ export default function ShareDialog({ children }: { children?: ReactNode }) {
<Tabs.Content value="excalidraw">
<Excalidraw />
</Tabs.Content>
<Tabs.Content value="json">
<JSONCanvasShare />
</Tabs.Content>
</Tabs.Root>
</Column>
</Content>
Expand Down Expand Up @@ -604,3 +615,41 @@ function InlineCode({ children }: { children: ReactNode }) {
</code>
);
}

function JSONCanvasShare() {
const title = useDocDetails("title", "flowchart.fun");

return (
<div className="grid gap-2">
<p className="leading-normal">
<Trans>
JSON Canvas is a JSON representation of your diagram used by{" "}
<a
href="https://obsidian.md/"
target="_blank"
className="text-blue-500 dark:text-blue-300 font-bold"
>
Obsidian
</a>{" "}
Canvas and other applications.
</Trans>
</p>
<Button2
color="blue"
onClick={() => {
if (!window.__cy) return;
const jsonCanvas = toJSONCanvas(window.__cy);
const blob = new Blob([JSON.stringify(jsonCanvas, null, 2)], {
type: "text/plain;charset=utf-8",
});
saveAs(blob, `${slugify(title)}.canvas`);
}}
className="w-full text-left text-neutral-500 dark:text-neutral-400 p-2 rounded border border-neutral-300 dark:border-neutral-700"
aria-label="Copy JSON Canvas"
leftIcon={<DownloadSimple size={16} />}
>
Download JSON Canvas
</Button2>
</div>
);
}
2 changes: 1 addition & 1 deletion app/src/lib/globalZ.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const globalZ = {
editWithAiButton: "z-[90]",
editWithAiButton: "z-[30]",
actionDropdownMobile: "z-[95]",
defaultDialog: "z-[100]",
};
2 changes: 1 addition & 1 deletion app/src/lib/templates/default-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ export const theme: FFTheme = {
};

export const cytoscapeStyle =
"$color: #31405b;\n$red: #e63946;\n$orange: #f4a261;\n$yellow: #f1fa3b;\n$green: #2a9d8f;\n$blue: #606ef6;\n$purple: #6d4a7c;\n$grey: #f2eded;\n\n:childless {\n font-weight: 500;\n}\n\n/** Start */\n:childless[in_degree < 1][out_degree > 0] {\n border-width: 0;\n shape: roundrectangle;\n background-color: $green;\n color: white;\n}\n\n/** Terminal */\n:childless[out_degree < 1][in_degree > 0] {\n border-width: 0;\n shape: roundrectangle;\n background-color: $red;\n color: white;\n}\n\n/** Branching */\n:childless[in_degree > 0][in_degree < 2][out_degree > 1] {\n shape: diamond;\n background-color: $blue;\n color: white;\n height: $width;\n border-width: 0;\n text-margin-y: 2;\n}\n\n/** Merging **/\n:childless[in_degree > 1][out_degree > 0][out_degree < 2] {\n}\n\n:childless.color_red {\n background-color: $red;\n color: white;\n}\n:childless.color_orange {\n background-color: $orange;\n color: white;\n}\n:childless.color_yellow {\n background-color: $yellow;\n}\n:childless.color_green {\n background-color: $green;\n color: white;\n}\n:childless.color_blue {\n background-color: $blue;\n color: white;\n}\n:childless.color_purple {\n background-color: $purple;\n color: white;\n}\n:childless.color_grey {\n background-color: $grey;\n}\n\n:parent.color_white {\n background-color: white;\n}\n:parent.color_grey {\n background-color: $grey;\n}";
"$color: #31405b;\n$red: #e63946;\n$orange: #f4a261;\n$yellow: #f1fa3b;\n$green: #2a9d8f;\n$blue: #606ef6;\n$purple: #6d4a7c;\n$grey: #f2eded;\n\n:childless {\n font-weight: 500;\n}\n\n/** Start - uncomment to use\n:childless[in_degree < 1][out_degree > 0] {\n border-width: 0;\n shape: roundrectangle;\n background-color: $green;\n color: white;\n}\n*/\n\n/** Terminal - uncomment to use\n:childless[out_degree < 1][in_degree > 0] {\n border-width: 0;\n shape: roundrectangle;\n background-color: $red;\n color: white;\n}\n*/\n\n/** Branching - uncomment to use\n:childless[in_degree > 0][in_degree < 2][out_degree > 1] {\n shape: diamond;\n background-color: $blue;\n color: white;\n height: $width;\n border-width: 0;\n text-margin-y: 2;\n}\n*/\n\n/** Merging **/\n:childless[in_degree > 1][out_degree > 0][out_degree < 2] {\n}\n\n:childless.color_red {\n background-color: $red;\n color: white;\n}\n:childless.color_orange {\n background-color: $orange;\n color: white;\n}\n:childless.color_yellow {\n background-color: $yellow;\n}\n:childless.color_green {\n background-color: $green;\n color: white;\n}\n:childless.color_blue {\n background-color: $blue;\n color: white;\n}\n:childless.color_purple {\n background-color: $purple;\n color: white;\n}\n:childless.color_grey {\n background-color: $grey;\n}\n\n:parent.color_white {\n background-color: white;\n}\n:parent.color_grey {\n background-color: $grey;\n}";
2 changes: 1 addition & 1 deletion app/src/lib/templates/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const templates: {
}[] = [
{
key: "default",
img: "template9.png",
img: "template21.png",
bgColor: "#FFFFFF",
title: () => `Default`,
promptType: "flowchart",
Expand Down
186 changes: 186 additions & 0 deletions app/src/lib/toJSONCanvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import cytoscape, { Core } from "cytoscape";

interface GenericNode {
id: string;
type: string;
x: number;
y: number;
width: number;
height: number;
color?: string;
}

interface TextNode extends GenericNode {
text: string;
}

interface FileNode extends GenericNode {
file: string;
subpath?: string;
}

interface LinkNode extends GenericNode {
url: string;
}

interface GroupNode extends GenericNode {
label?: string;
background?: string;
backgroundStyle?: "cover" | "ratio" | "repeat";
}

type Side = "top" | "right" | "bottom" | "left";

interface Edge {
id: string;
fromNode: string;
fromSide?: Side;
fromEnd?: "none" | "arrow";
toNode: string;
toSide?: Side;
toEnd?: "none" | "arrow";
color?: string;
label?: string;
}

interface JSONCanvas {
nodes?: Array<TextNode | FileNode | LinkNode | GroupNode>;
edges?: Edge[];
}

const HEIGHT_PAD = 46;

export function toJSONCanvas(cy: Core): JSONCanvas {
const jsonCanvas: JSONCanvas = {
nodes: [],
edges: [],
};

cy.nodes().forEach((node) => {
const position = node.position();
const data = node.data();

// Assuming 'group' nodes are represented in cytoscape with a 'parent' field
const type = node.isParent() ? "group" : "text";

const genericNode: TextNode = {
id: node.id(),
type,
x: position.x,
y: position.y,
width: Math.round(node.width() + HEIGHT_PAD),
height: Math.round(node.height() + HEIGHT_PAD),
text: data.label,
};

// get the node rendered background color
const renderedStyle = node.renderedStyle();
const backgroundColor = renderedStyle.backgroundColor;
if (backgroundColor !== "transparent") {
const hex = rgbToHex(backgroundColor);
if (hex !== "#ffffff") {
genericNode.color = hex;
}
}

jsonCanvas.nodes?.push({
...genericNode,
});
});

cy.edges().forEach((edge) => {
const data = edge.data();

const { fromSide, toSide } = getSideFromEndpoints(edge);

jsonCanvas.edges?.push({
id: edge.id(),
fromNode: data.source,
toNode: data.target,
// color: data.color ? { color: data.color } : undefined,
label: data.label,
fromSide,
toSide,
fromEnd: data.fromEnd,
toEnd: data.toEnd,
});
});

return jsonCanvas;
}

type Direction = "down" | "left" | "right" | "up";

/**
* Use the sourceEndpoint and targetEndpoint
* to determine if the edge is more right, down, left, or up,
* and return the according fromSide and toSide
*/
function getSideFromEndpoints(edge: cytoscape.EdgeSingular): {
fromSide: Side;
toSide: Side;
} {
const source = getBoundingBox(edge.source());
const target = getBoundingBox(edge.target());

// Give a score to each direction based on the distance
// of opposite sides of the source and target nodes
const scores: Record<Direction, number> = {
down: target.top - source.bottom,
left: source.left - target.right,
right: target.left - source.right,
up: source.top - target.bottom,
};

// Get the direction with the highest score
const maxScore = Math.max(...Object.values(scores));
const direction = Object.keys(scores).find(
(direction) => scores[direction as Direction] === maxScore
) as Direction;

// Return the fromSide and toSide based on the direction
switch (direction) {
case "down":
return { fromSide: "bottom", toSide: "top" };
case "left":
return { fromSide: "left", toSide: "right" };
case "right":
return { fromSide: "right", toSide: "left" };
case "up":
return { fromSide: "top", toSide: "bottom" };
}
}

/**
* Given a node, get the top-left corner of the node
* and the bottom-right corner of the node and return
* an object with top, left, bottom, and right properties
*/
function getBoundingBox(node: cytoscape.NodeSingular): {
top: number;
left: number;
bottom: number;
right: number;
} {
const position = node.position();
const width = node.width();
const height = node.height();

return {
top: position.y,
left: position.x,
bottom: position.y + height,
right: position.x + width,
};
}

/**
* Converts an rgb string in the format rgb(255,255,255)
* to a hex string in the format #ffffff
*/
function rgbToHex(rgb: string): string {
const [r, g, b] = rgb
.match(/\d+/g)!
.map((value) => parseInt(value, 10).toString(16).padStart(2, "0"));
return `#${r}${g}${b}`;
}
2 changes: 1 addition & 1 deletion app/src/locales/de/messages.js

Large diffs are not rendered by default.

Loading

0 comments on commit b646d30

Please sign in to comment.